mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-22 14:57: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>
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.asset
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.bindMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedXcm
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.bindVersionedXcm
|
||||
|
||||
fun bindVersionedLocatableMultiAsset(decoded: Any?): VersionedXcm<LocatableMultiAsset> {
|
||||
return bindVersionedXcm(decoded, ::bindLocatableMultiAsset)
|
||||
}
|
||||
|
||||
fun bindLocatableMultiAsset(decoded: Any?, xcmVersion: XcmVersion): LocatableMultiAsset {
|
||||
val asStruct = decoded.castToStruct()
|
||||
|
||||
return LocatableMultiAsset(
|
||||
location = bindMultiLocation(asStruct["location"]),
|
||||
assetId = bindMultiAssetId(asStruct["asset_id"], xcmVersion)
|
||||
)
|
||||
}
|
||||
|
||||
fun bindMultiAssetId(decoded: Any?, xcmVersion: XcmVersion): MultiAssetId {
|
||||
// V4 removed variants of MultiAssetId, leaving only flattened value of Concrete
|
||||
val locationInstance = if (xcmVersion >= XcmVersion.V4) {
|
||||
decoded
|
||||
} else {
|
||||
extractConcreteLocation(decoded)
|
||||
}
|
||||
|
||||
return MultiAssetId(bindMultiLocation(locationInstance))
|
||||
}
|
||||
|
||||
private fun extractConcreteLocation(decoded: Any?): Any? {
|
||||
val variant = decoded.castToDictEnum()
|
||||
require(variant.name == "Concrete") {
|
||||
"Asset ids besides Concrete are not supported"
|
||||
}
|
||||
|
||||
return variant.value
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.asset
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedXcm
|
||||
|
||||
class LocatableMultiAsset(
|
||||
|
||||
val location: RelativeMultiLocation,
|
||||
|
||||
val assetId: MultiAssetId
|
||||
)
|
||||
|
||||
typealias VersionedLocatableMultiAsset = VersionedXcm<LocatableMultiAsset>
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.asset
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindList
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.cast
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.incompatible
|
||||
import io.novafoundation.nova.common.utils.scale.ToDynamicScaleInstance
|
||||
import io.novafoundation.nova.common.utils.structOf
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedToDynamicScaleInstance
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedXcm
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.bindVersionedXcm
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
import java.math.BigInteger
|
||||
|
||||
data class MultiAsset private constructor(
|
||||
val id: MultiAssetId,
|
||||
val fungibility: Fungibility,
|
||||
) : VersionedToDynamicScaleInstance {
|
||||
|
||||
companion object {
|
||||
|
||||
fun bind(decodedInstance: Any?, xcmVersion: XcmVersion): MultiAsset {
|
||||
val asStruct = decodedInstance.castToStruct()
|
||||
return MultiAsset(
|
||||
id = bindMultiAssetId(asStruct["id"], xcmVersion),
|
||||
fungibility = Fungibility.bind(asStruct["fun"])
|
||||
)
|
||||
}
|
||||
|
||||
fun from(
|
||||
multiLocation: RelativeMultiLocation,
|
||||
amount: BalanceOf
|
||||
): MultiAsset {
|
||||
// Substrate doesn't allow zero balance starting from xcm v3
|
||||
val positiveAmount = amount.coerceAtLeast(BigInteger.ONE)
|
||||
|
||||
return MultiAsset(
|
||||
id = MultiAssetId(multiLocation),
|
||||
fungibility = Fungibility.Fungible(positiveAmount)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Fungibility : ToDynamicScaleInstance {
|
||||
|
||||
companion object {
|
||||
|
||||
fun bind(decodedInstance: Any?): Fungibility {
|
||||
val asEnum = decodedInstance.castToDictEnum()
|
||||
|
||||
return when (asEnum.name) {
|
||||
"Fungible" -> Fungible(bindNumber(asEnum.value))
|
||||
else -> incompatible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Fungible(val amount: BalanceOf) : Fungibility() {
|
||||
|
||||
override fun toEncodableInstance(): Any {
|
||||
return DictEnum.Entry(name = "Fungible", value = amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return structOf(
|
||||
"fun" to fungibility.toEncodableInstance(),
|
||||
"id" to id.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun MultiAsset.requireFungible(): MultiAsset.Fungibility.Fungible {
|
||||
return fungibility.cast()
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class MultiAssets(val value: List<MultiAsset>) : VersionedToDynamicScaleInstance {
|
||||
|
||||
companion object {
|
||||
|
||||
fun bind(decodedInstance: Any?, xcmVersion: XcmVersion): MultiAssets {
|
||||
val assets = bindList(decodedInstance) { MultiAsset.bind(it, xcmVersion) }
|
||||
return MultiAssets(assets)
|
||||
}
|
||||
|
||||
fun bindVersioned(decodedInstance: Any?): VersionedMultiAssets {
|
||||
return bindVersionedXcm(decodedInstance, MultiAssets::bind)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(vararg assets: MultiAsset) : this(assets.toList())
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return value.map { it.toEncodableInstance(xcmVersion) }
|
||||
}
|
||||
}
|
||||
|
||||
fun List<MultiAsset>.intoMultiAssets(): MultiAssets {
|
||||
return MultiAssets(this)
|
||||
}
|
||||
|
||||
fun MultiAsset.intoMultiAssets(): MultiAssets {
|
||||
return MultiAssets(listOf(this))
|
||||
}
|
||||
|
||||
typealias VersionedMultiAsset = VersionedXcm<MultiAsset>
|
||||
typealias VersionedMultiAssets = VersionedXcm<MultiAssets>
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.asset
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedToDynamicScaleInstance
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
|
||||
sealed class MultiAssetFilter : VersionedToDynamicScaleInstance {
|
||||
|
||||
companion object {
|
||||
|
||||
fun singleCounted(): Wild.AllCounted {
|
||||
return Wild.AllCounted(assetsCount = 1)
|
||||
}
|
||||
}
|
||||
|
||||
class Definite(val assets: MultiAssets) : MultiAssetFilter() {
|
||||
|
||||
constructor(asset: MultiAsset) : this(asset.intoMultiAssets())
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any? {
|
||||
return DictEnum.Entry("Definite", assets.toEncodableInstance(xcmVersion))
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Wild : MultiAssetFilter() {
|
||||
|
||||
/**
|
||||
* Filter to use all assets from the holding register
|
||||
*
|
||||
* !!! Important !!!
|
||||
* Weight of this instruction is bounded by maximum number of assets usable per instruction,
|
||||
* which can be 100 in some runtimes.
|
||||
* This might result in sever overestimation of instruction weight and thus, the fee.
|
||||
*
|
||||
* Please use other variations that put explicit desired bound like [AllCounted] whenever possible
|
||||
*/
|
||||
object All : Wild() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "All"
|
||||
}
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "Wild",
|
||||
value = DictEnum.Entry(
|
||||
name = "All",
|
||||
value = null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to use first [assetsCount] assets from the holding register
|
||||
*/
|
||||
data class AllCounted(val assetsCount: Int) : Wild() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "Wild",
|
||||
value = DictEnum.Entry(
|
||||
name = "AllCounted",
|
||||
value = assetsCount.toBigInteger()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.asset
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedToDynamicScaleInstance
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
|
||||
@JvmInline
|
||||
value class MultiAssetId(val multiLocation: RelativeMultiLocation) : VersionedToDynamicScaleInstance {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any? {
|
||||
// V4 removed variants of MultiAssetId, leaving only flattened value of Concrete
|
||||
return if (xcmVersion >= XcmVersion.V4) {
|
||||
multiLocation.toEncodableInstance(xcmVersion)
|
||||
} else {
|
||||
DictEnum.Entry(
|
||||
name = "Concrete",
|
||||
value = multiLocation.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MultiAssetId.withAmount(amount: BalanceOf): MultiAsset {
|
||||
return MultiAsset.from(multiLocation, amount)
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.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.MultiAssetFilter.Wild.All
|
||||
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.intoMultiAssets
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.withAmount
|
||||
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.builder.fees.UnsupportedMeasureXcmFees
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedXcmMessage
|
||||
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.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.weight.WeightLimit
|
||||
|
||||
interface XcmBuilder : XcmContext {
|
||||
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
initial: ChainLocation,
|
||||
xcmVersion: XcmVersion,
|
||||
measureXcmFees: MeasureXcmFees
|
||||
): XcmBuilder
|
||||
}
|
||||
|
||||
fun payFees(payFeesMode: PayFeesMode)
|
||||
|
||||
fun withdrawAsset(assets: MultiAssets)
|
||||
|
||||
fun buyExecution(fees: MultiAsset, weightLimit: WeightLimit)
|
||||
|
||||
// We only support depositing to a accountId. We might extend it in the future with no issues
|
||||
// but we keep the support limited to simplify implementation
|
||||
fun depositAsset(assets: MultiAssetFilter, beneficiary: AccountIdKey)
|
||||
|
||||
// Performs context change
|
||||
fun transferReserveAsset(assets: MultiAssets, dest: ChainLocation)
|
||||
|
||||
// Performs context change
|
||||
fun initiateReserveWithdraw(assets: MultiAssetFilter, reserve: ChainLocation)
|
||||
|
||||
// Performs context change
|
||||
fun depositReserveAsset(assets: MultiAssetFilter, dest: ChainLocation)
|
||||
|
||||
fun initiateTeleport(assets: MultiAssetFilter, dest: ChainLocation)
|
||||
|
||||
suspend fun build(): VersionedXcmMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used when `payFees` is not expected to be used
|
||||
*/
|
||||
fun XcmBuilder.Factory.createWithoutFeesMeasurement(
|
||||
initial: ChainLocation,
|
||||
xcmVersion: XcmVersion,
|
||||
): XcmBuilder {
|
||||
return create(initial, xcmVersion, UnsupportedMeasureXcmFees())
|
||||
}
|
||||
|
||||
suspend fun XcmBuilder.Factory.buildXcmWithoutFeesMeasurement(
|
||||
initial: ChainLocation,
|
||||
xcmVersion: XcmVersion,
|
||||
building: XcmBuilder.() -> Unit
|
||||
): VersionedXcmMessage {
|
||||
return createWithoutFeesMeasurement(initial, xcmVersion)
|
||||
.apply(building)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun XcmBuilder.withdrawAsset(asset: AbsoluteMultiLocation, amount: BalanceOf) {
|
||||
withdrawAsset(MultiAsset.from(asset.relativeToLocal(), amount).intoMultiAssets())
|
||||
}
|
||||
|
||||
fun XcmBuilder.transferReserveAsset(asset: AbsoluteMultiLocation, amount: BalanceOf, dest: ChainLocation) {
|
||||
transferReserveAsset(MultiAsset.from(asset.relativeToLocal(), amount).intoMultiAssets(), dest)
|
||||
}
|
||||
|
||||
fun XcmBuilder.buyExecution(asset: AbsoluteMultiLocation, amount: BalanceOf, weightLimit: WeightLimit) {
|
||||
buyExecution(MultiAsset.from(asset.relativeToLocal(), amount), weightLimit)
|
||||
}
|
||||
|
||||
fun XcmBuilder.depositAllAssetsTo(beneficiary: AccountIdKey) {
|
||||
depositAsset(All, beneficiary)
|
||||
}
|
||||
|
||||
fun XcmBuilder.payFeesIn(assetId: AssetLocation) {
|
||||
payFees(PayFeesMode.Measured(assetId))
|
||||
}
|
||||
|
||||
fun XcmBuilder.payFees(assetId: MultiAssetId, exactFees: BalanceOf) {
|
||||
payFees(PayFeesMode.Exact(assetId.withAmount(exactFees)))
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.builder
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.AbsoluteMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.ChainLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
|
||||
interface XcmContext {
|
||||
|
||||
val xcmVersion: XcmVersion
|
||||
|
||||
val currentLocation: ChainLocation
|
||||
}
|
||||
|
||||
fun XcmContext.localViewOf(location: AbsoluteMultiLocation): RelativeMultiLocation {
|
||||
return location.fromPointOfViewOf(currentLocation.location)
|
||||
}
|
||||
|
||||
context(XcmContext)
|
||||
fun AbsoluteMultiLocation.relativeToLocal(): RelativeMultiLocation {
|
||||
return localViewOf(this)
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.builder.fees
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.XcmBuilder
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.AssetLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.ChainLocation
|
||||
|
||||
/**
|
||||
* Measure fees for a given xcm message. Used by [XcmBuilder] when processing [XcmBuilder.payFees]
|
||||
* with [PayFeesMode.Measured] specified
|
||||
*/
|
||||
interface MeasureXcmFees {
|
||||
|
||||
suspend fun measureFees(
|
||||
message: VersionedXcmMessage,
|
||||
feeAsset: AssetLocation,
|
||||
chainLocation: ChainLocation,
|
||||
): BalanceOf
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.builder.fees
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.MultiAsset
|
||||
import io.novafoundation.nova.feature_xcm_api.builder.XcmBuilder
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.AssetLocation
|
||||
|
||||
/**
|
||||
* Specifies how [XcmBuilder] should specify fees for PayFees instruction
|
||||
*/
|
||||
sealed class PayFeesMode {
|
||||
|
||||
/**
|
||||
* Fees should be measured when building the xcm by calling provided [MeasureXcmFees] implementation
|
||||
*/
|
||||
class Measured(val feeAssetId: AssetLocation) : PayFeesMode()
|
||||
|
||||
/**
|
||||
* Should use exactly [fee] when specifying fees
|
||||
*/
|
||||
class Exact(val fee: MultiAsset) : PayFeesMode()
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.builder.fees
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.BalanceOf
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.AssetLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.ChainLocation
|
||||
|
||||
class UnsupportedMeasureXcmFees : MeasureXcmFees {
|
||||
override suspend fun measureFees(
|
||||
message: VersionedXcmMessage,
|
||||
feeAsset: AssetLocation,
|
||||
chainLocation: ChainLocation
|
||||
): BalanceOf {
|
||||
error("Measurement not supported")
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.chain
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.AbsoluteMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.ChainLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.chainLocation
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import java.math.BigInteger
|
||||
|
||||
class XcmChain(
|
||||
val parachainId: BigInteger?,
|
||||
val chain: Chain
|
||||
)
|
||||
|
||||
fun XcmChain.absoluteLocation(): AbsoluteMultiLocation {
|
||||
return AbsoluteMultiLocation.chainLocation(parachainId)
|
||||
}
|
||||
|
||||
fun XcmChain.isRelay(): Boolean {
|
||||
return parachainId == null
|
||||
}
|
||||
|
||||
fun XcmChain.isSystemChain(): Boolean {
|
||||
return parachainId != null && parachainId.toInt() in 1000 until 2000
|
||||
}
|
||||
|
||||
fun XcmChain.chainLocation(): ChainLocation {
|
||||
return ChainLocation(chain.id, absoluteLocation())
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.converter
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
|
||||
interface MultiLocationConverter {
|
||||
|
||||
suspend fun toMultiLocation(chainAsset: Chain.Asset): RelativeMultiLocation?
|
||||
|
||||
suspend fun toChainAsset(multiLocation: RelativeMultiLocation): Chain.Asset?
|
||||
}
|
||||
|
||||
suspend fun MultiLocationConverter.toMultiLocationOrThrow(chainAsset: Chain.Asset): RelativeMultiLocation {
|
||||
return toMultiLocation(chainAsset) ?: error("Failed to convert asset location")
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.converter
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
interface MultiLocationConverterFactory {
|
||||
|
||||
fun defaultAsync(chain: Chain, coroutineScope: CoroutineScope): MultiLocationConverter
|
||||
|
||||
suspend fun defaultSync(chain: Chain): MultiLocationConverter
|
||||
|
||||
suspend fun resolveLocalAssets(chain: Chain): MultiLocationConverter
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.converter.chain
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
|
||||
interface ChainMultiLocationConverter {
|
||||
|
||||
suspend fun toChain(multiLocation: RelativeMultiLocation): Chain?
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.converter.chain
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
|
||||
interface ChainMultiLocationConverterFactory {
|
||||
|
||||
fun resolveSelfAndChildrenParachains(self: Chain): ChainMultiLocationConverter
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.di
|
||||
|
||||
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
|
||||
|
||||
interface XcmFeatureApi {
|
||||
|
||||
val assetMultiLocationConverterFactory: MultiLocationConverterFactory
|
||||
|
||||
val chainMultiLocationConverterFactory: ChainMultiLocationConverterFactory
|
||||
|
||||
val xcmVersionDetector: XcmVersionDetector
|
||||
|
||||
val dryRunApi: DryRunApi
|
||||
|
||||
val xcmPaymentApi: XcmPaymentApi
|
||||
|
||||
val xcmBuilderFactory: XcmBuilder.Factory
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.extrinsic
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.WeightV2
|
||||
import io.novafoundation.nova.common.utils.RuntimeContext
|
||||
import io.novafoundation.nova.common.utils.composeCall
|
||||
import io.novafoundation.nova.common.utils.xcmPalletName
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.toEncodableInstance
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
|
||||
context(RuntimeContext)
|
||||
fun composeXcmExecute(
|
||||
message: VersionedXcmMessage,
|
||||
maxWeight: WeightV2,
|
||||
): GenericCall.Instance {
|
||||
return composeCall(
|
||||
moduleName = runtime.metadata.xcmPalletName(),
|
||||
callName = "execute",
|
||||
arguments = mapOf(
|
||||
"message" to message.toEncodableInstance(),
|
||||
"max_weight" to maxWeight.toEncodableInstance()
|
||||
)
|
||||
)
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.message
|
||||
|
||||
import io.novafoundation.nova.common.utils.structOf
|
||||
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.MultiAssets
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedToDynamicScaleInstance
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedXcm
|
||||
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.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
import java.math.BigInteger
|
||||
|
||||
@JvmInline
|
||||
value class XcmMessage(val instructions: List<XcmInstruction>) : VersionedToDynamicScaleInstance {
|
||||
|
||||
constructor(vararg instructions: XcmInstruction) : this(instructions.toList())
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any? {
|
||||
return instructions.map { it.toEncodableInstance(xcmVersion) }
|
||||
}
|
||||
}
|
||||
|
||||
fun List<XcmInstruction>.asXcmMessage(): XcmMessage = XcmMessage(this)
|
||||
|
||||
fun List<XcmInstruction>.asVersionedXcmMessage(version: XcmVersion): VersionedXcmMessage = asXcmMessage().versionedXcm(version)
|
||||
|
||||
sealed class XcmInstruction : VersionedToDynamicScaleInstance {
|
||||
|
||||
data class WithdrawAsset(val assets: MultiAssets) : XcmInstruction() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any? {
|
||||
return DictEnum.Entry(
|
||||
name = "WithdrawAsset",
|
||||
value = assets.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class DepositAsset(
|
||||
val assets: MultiAssetFilter,
|
||||
val beneficiary: RelativeMultiLocation
|
||||
) : XcmInstruction() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any? {
|
||||
return DictEnum.Entry(
|
||||
name = "DepositAsset",
|
||||
value = structOf(
|
||||
"assets" to assets.toEncodableInstance(xcmVersion),
|
||||
"beneficiary" to beneficiary.toEncodableInstance(xcmVersion),
|
||||
// Used in XCM V2 and below. We put 1 here since we only support cases for transferring a single asset
|
||||
"max_assets" to BigInteger.ONE,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class BuyExecution(val fees: MultiAsset, val weightLimit: WeightLimit) : XcmInstruction() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "BuyExecution",
|
||||
value = structOf(
|
||||
"fees" to fees.toEncodableInstance(xcmVersion),
|
||||
// xcm v2 always uses v1 weights
|
||||
"weight_limit" to weightLimit.toEncodableInstance()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object ClearOrigin : XcmInstruction() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "ClearOrigin",
|
||||
value = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ReserveAssetDeposited(val assets: MultiAssets) : XcmInstruction() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "ReserveAssetDeposited",
|
||||
value = assets.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ReceiveTeleportedAsset(val assets: MultiAssets) : XcmInstruction() {
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "ReceiveTeleportedAsset",
|
||||
value = assets.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class InitiateReserveWithdraw(
|
||||
val assets: MultiAssetFilter,
|
||||
val reserve: RelativeMultiLocation,
|
||||
val xcm: XcmMessage
|
||||
) : XcmInstruction() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "InitiateReserveWithdraw",
|
||||
value = structOf(
|
||||
"assets" to assets.toEncodableInstance(xcmVersion),
|
||||
"reserve" to reserve.toEncodableInstance(xcmVersion),
|
||||
"xcm" to xcm.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class TransferReserveAsset(
|
||||
val assets: MultiAssets,
|
||||
val dest: RelativeMultiLocation,
|
||||
val xcm: XcmMessage
|
||||
) : XcmInstruction() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "TransferReserveAsset",
|
||||
value = structOf(
|
||||
"assets" to assets.toEncodableInstance(xcmVersion),
|
||||
"dest" to dest.toEncodableInstance(xcmVersion),
|
||||
"xcm" to xcm.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class DepositReserveAsset(
|
||||
val assets: MultiAssetFilter,
|
||||
val dest: RelativeMultiLocation,
|
||||
val xcm: XcmMessage
|
||||
) : XcmInstruction() {
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "DepositReserveAsset",
|
||||
value = structOf(
|
||||
"assets" to assets.toEncodableInstance(xcmVersion),
|
||||
// Used in XCM V2 and below. We put 1 here since we only support cases for transferring a single asset
|
||||
"max_assets" to BigInteger.ONE,
|
||||
"dest" to dest.toEncodableInstance(xcmVersion),
|
||||
"xcm" to xcm.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class PayFees(val fees: MultiAsset) : XcmInstruction() {
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "PayFees",
|
||||
value = structOf(
|
||||
"fees" to fees.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class InitiateTeleport(
|
||||
val assets: MultiAssetFilter,
|
||||
val dest: RelativeMultiLocation,
|
||||
val xcm: XcmMessage
|
||||
) : XcmInstruction() {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return DictEnum.Entry(
|
||||
name = "InitiateTeleport",
|
||||
value = structOf(
|
||||
"assets" to assets.toEncodableInstance(xcmVersion),
|
||||
"dest" to dest.toEncodableInstance(xcmVersion),
|
||||
"xcm" to xcm.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias VersionedXcmMessage = VersionedXcm<XcmMessage>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.message
|
||||
|
||||
import io.novafoundation.nova.common.utils.scale.DynamicScaleInstance
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedXcm
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.bindVersionedXcm
|
||||
|
||||
typealias XcmMessageRaw = DynamicScaleInstance
|
||||
typealias VersionedRawXcmMessage = VersionedXcm<XcmMessageRaw>
|
||||
|
||||
fun bindVersionedRawXcmMessage(decodedInstance: Any?) = bindVersionedXcm(decodedInstance) { inner, _ ->
|
||||
DynamicScaleInstance(inner)
|
||||
}
|
||||
|
||||
fun bindRawXcmMessage(decodedInstance: Any?) = DynamicScaleInstance(decodedInstance)
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.multiLocation
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.common.utils.collectionIndexOrNull
|
||||
|
||||
class AbsoluteMultiLocation(
|
||||
interior: Interior,
|
||||
) : MultiLocation(interior) {
|
||||
|
||||
companion object;
|
||||
|
||||
constructor(vararg junctions: Junction) : this(junctions.toList().toInterior())
|
||||
|
||||
fun toRelative(): RelativeMultiLocation {
|
||||
return RelativeMultiLocation(parents = 0, interior = interior)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reanchor given location to a point of view of given `pov` location
|
||||
* Basic algorithm idea:
|
||||
* We find the last common ancestor and consider the target location to be "up to ancestor and down to self":
|
||||
* 1. Find last common ancestor between `this` and `pov`
|
||||
* 2. Use all junctions after common ancestor as result junctions
|
||||
* 3. Use difference between len(target.junctions) and common_ancestor_idx
|
||||
* to determine how many "up" hops are needed to reach common ancestor
|
||||
*/
|
||||
fun fromPointOfViewOf(pov: AbsoluteMultiLocation): RelativeMultiLocation {
|
||||
val lastCommonIndex = findLastCommonJunctionIndex(pov)
|
||||
val firstDistinctIndex = lastCommonIndex?.let { it + 1 } ?: 0
|
||||
|
||||
val parents = pov.junctions.size - firstDistinctIndex
|
||||
val junctions = junctions.drop(firstDistinctIndex)
|
||||
|
||||
return RelativeMultiLocation(parents, junctions.toInterior())
|
||||
}
|
||||
|
||||
private fun findLastCommonJunctionIndex(other: AbsoluteMultiLocation): Int? {
|
||||
return junctions.zip(other.junctions).indexOfLast { (selfJunction, otherJunction) ->
|
||||
selfJunction == otherJunction
|
||||
}.collectionIndexOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun AbsoluteMultiLocation.Companion.chainLocation(parachainId: ParaId?): AbsoluteMultiLocation {
|
||||
return listOfNotNull(parachainId?.let(MultiLocation.Junction::ParachainId)).asLocation()
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.multiLocation
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.asset.MultiAssetId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId
|
||||
|
||||
class AssetLocation(
|
||||
val assetId: FullChainAssetId,
|
||||
val location: AbsoluteMultiLocation
|
||||
)
|
||||
|
||||
fun AssetLocation.multiAssetIdOn(chainLocation: ChainLocation): MultiAssetId {
|
||||
val relativeMultiLocation = location.fromPointOfViewOf(chainLocation.location)
|
||||
return MultiAssetId(relativeMultiLocation)
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.multiLocation
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
class ChainLocation(
|
||||
val chainId: ChainId,
|
||||
val location: AbsoluteMultiLocation
|
||||
)
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.multiLocation
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ParaId
|
||||
import io.novafoundation.nova.common.utils.HexString
|
||||
import io.novafoundation.nova.common.utils.isAscending
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.extensions.tryFindNonNull
|
||||
import java.math.BigInteger
|
||||
|
||||
abstract class MultiLocation(
|
||||
open val interior: Interior
|
||||
) {
|
||||
|
||||
sealed class Interior {
|
||||
|
||||
object Here : Interior()
|
||||
|
||||
class Junctions(junctions: List<Junction>) : Interior() {
|
||||
val junctions = junctions.sorted()
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is Junctions) return false
|
||||
return junctions == other.junctions
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return junctions.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return junctions.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Junction {
|
||||
|
||||
data class ParachainId(val id: ParaId) : Junction() {
|
||||
|
||||
constructor(id: Int) : this(id.toBigInteger())
|
||||
}
|
||||
|
||||
data class GeneralKey(val key: HexString) : Junction()
|
||||
|
||||
data class PalletInstance(val index: BigInteger) : Junction()
|
||||
|
||||
data class GeneralIndex(val index: BigInteger) : Junction()
|
||||
|
||||
data class AccountKey20(val accountId: AccountIdKey) : Junction()
|
||||
|
||||
data class AccountId32(val accountId: AccountIdKey) : Junction()
|
||||
|
||||
data class GlobalConsensus(val networkId: NetworkId) : Junction()
|
||||
|
||||
object Unsupported : Junction()
|
||||
}
|
||||
|
||||
sealed class NetworkId {
|
||||
|
||||
data class Substrate(val genesisHash: ChainId) : NetworkId()
|
||||
|
||||
data class Ethereum(val chainId: Int) : NetworkId()
|
||||
}
|
||||
}
|
||||
|
||||
val Junction.order
|
||||
get() = when (this) {
|
||||
is Junction.GlobalConsensus -> 0
|
||||
|
||||
is Junction.ParachainId -> 1
|
||||
|
||||
// All of these are on the same "level" - mutually exhaustive
|
||||
is Junction.PalletInstance,
|
||||
is Junction.AccountKey20,
|
||||
is Junction.AccountId32 -> 2
|
||||
|
||||
is Junction.GeneralKey,
|
||||
is Junction.GeneralIndex -> 3
|
||||
|
||||
Junction.Unsupported -> Int.MAX_VALUE
|
||||
}
|
||||
|
||||
val MultiLocation.junctions: List<Junction>
|
||||
get() = when (val interior = interior) {
|
||||
MultiLocation.Interior.Here -> emptyList()
|
||||
is MultiLocation.Interior.Junctions -> interior.junctions
|
||||
}
|
||||
|
||||
fun List<Junction>.toInterior() = when (size) {
|
||||
0 -> MultiLocation.Interior.Here
|
||||
else -> MultiLocation.Interior.Junctions(this)
|
||||
}
|
||||
|
||||
fun Junction.toInterior() = MultiLocation.Interior.Junctions(listOf(this))
|
||||
|
||||
fun MultiLocation.Interior.isHere() = this is MultiLocation.Interior.Here
|
||||
|
||||
fun MultiLocation.accountId(): AccountIdKey? {
|
||||
return junctions.tryFindNonNull {
|
||||
when (it) {
|
||||
is Junction.AccountId32 -> it.accountId
|
||||
is Junction.AccountKey20 -> it.accountId
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MultiLocation.Interior.asLocation(): AbsoluteMultiLocation {
|
||||
return AbsoluteMultiLocation(this)
|
||||
}
|
||||
|
||||
fun List<Junction>.asLocation(): AbsoluteMultiLocation {
|
||||
return toInterior().asLocation()
|
||||
}
|
||||
|
||||
fun Junction.asLocation(): AbsoluteMultiLocation {
|
||||
return toInterior().asLocation()
|
||||
}
|
||||
|
||||
operator fun RelativeMultiLocation.plus(suffix: RelativeMultiLocation): RelativeMultiLocation {
|
||||
require(suffix.parents == 0) {
|
||||
"Appending multi location that has parents is not supported"
|
||||
}
|
||||
|
||||
val newJunctions = junctions + suffix.junctions
|
||||
require(newJunctions.isAscending(compareBy { it.order })) {
|
||||
"Cannot append this multi location due to conflicting junctions"
|
||||
}
|
||||
|
||||
return RelativeMultiLocation(
|
||||
parents = parents,
|
||||
interior = newJunctions.toInterior()
|
||||
)
|
||||
}
|
||||
|
||||
fun AccountIdKey.toMultiLocation() = RelativeMultiLocation(
|
||||
parents = 0,
|
||||
interior = Junctions(
|
||||
when (value.size) {
|
||||
32 -> Junction.AccountId32(this)
|
||||
20 -> Junction.AccountKey20(this)
|
||||
else -> throw IllegalArgumentException("Unsupported account id length: ${value.size}")
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
fun Junctions(vararg junctions: Junction) = MultiLocation.Interior.Junctions(junctions.toList())
|
||||
|
||||
fun MultiLocation.paraIdOrNull(): ParaId? {
|
||||
return junctions.filterIsInstance<Junction.ParachainId>()
|
||||
.firstOrNull()
|
||||
?.id
|
||||
}
|
||||
|
||||
private fun List<Junction>.sorted(): List<Junction> {
|
||||
return sortedBy(Junction::order)
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.multiLocation
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.address.intoKey
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindByteArray
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindInt
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindList
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.utils.HexString
|
||||
import io.novafoundation.nova.common.utils.padEnd
|
||||
import io.novafoundation.nova.common.utils.structOf
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.NetworkId
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedXcm
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.bindVersionedXcm
|
||||
import io.novafoundation.nova.runtime.ext.Geneses
|
||||
import io.novafoundation.nova.runtime.ext.Ids
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novasama.substrate_sdk_android.encrypt.json.copyBytes
|
||||
import io.novasama.substrate_sdk_android.extensions.fromHex
|
||||
import io.novasama.substrate_sdk_android.extensions.toHexString
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.Struct
|
||||
|
||||
// ------ Decode ------
|
||||
|
||||
fun bindMultiLocation(instance: Any?): RelativeMultiLocation {
|
||||
val asStruct = instance.castToStruct()
|
||||
|
||||
return RelativeMultiLocation(
|
||||
parents = bindInt(asStruct["parents"]),
|
||||
interior = bindInterior((asStruct["interior"]))
|
||||
)
|
||||
}
|
||||
|
||||
fun bindVersionedMultiLocation(instance: Any?): VersionedXcm<RelativeMultiLocation> {
|
||||
return bindVersionedXcm(instance) { inner, _ -> bindMultiLocation(inner) }
|
||||
}
|
||||
|
||||
private fun bindInterior(instance: Any?): MultiLocation.Interior {
|
||||
val asDictEnum = instance.castToDictEnum()
|
||||
|
||||
return when (asDictEnum.name) {
|
||||
"Here" -> MultiLocation.Interior.Here
|
||||
|
||||
else -> {
|
||||
val junctions = bindJunctions(asDictEnum.value)
|
||||
MultiLocation.Interior.Junctions(junctions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindJunctions(instance: Any?): List<Junction> {
|
||||
// Note that Interior.X1 is encoded differently in XCM v3 (a single junction) and V4 (single-element list)
|
||||
if (instance is List<*>) {
|
||||
return bindList(instance, ::bindJunction)
|
||||
} else {
|
||||
return listOf(bindJunction(instance))
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindJunction(instance: Any?): Junction {
|
||||
val asDictEnum = instance.castToDictEnum()
|
||||
|
||||
return when (asDictEnum.name) {
|
||||
"GeneralKey" -> Junction.GeneralKey(bindGeneralKey(asDictEnum.value))
|
||||
"PalletInstance" -> Junction.PalletInstance(bindNumber(asDictEnum.value))
|
||||
"Parachain" -> Junction.ParachainId(bindNumber(asDictEnum.value))
|
||||
"GeneralIndex" -> Junction.GeneralIndex(bindNumber(asDictEnum.value))
|
||||
"GlobalConsensus" -> bindGlobalConsensusJunction(asDictEnum.value)
|
||||
"AccountKey20" -> Junction.AccountKey20(bindAccountIdJunction(asDictEnum.value, accountIdKey = "key"))
|
||||
"AccountId32" -> Junction.AccountId32(bindAccountIdJunction(asDictEnum.value, accountIdKey = "id"))
|
||||
|
||||
else -> Junction.Unsupported
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindGeneralKey(instance: Any?): HexString {
|
||||
val keyBytes = if (instance is Struct.Instance) {
|
||||
// v3+ structure
|
||||
val keyLength = bindInt(instance["length"])
|
||||
val keyPadded = bindByteArray(instance["data"])
|
||||
|
||||
keyPadded.copyBytes(0, keyLength)
|
||||
} else {
|
||||
bindByteArray(instance)
|
||||
}
|
||||
|
||||
return keyBytes.toHexString(withPrefix = true)
|
||||
}
|
||||
|
||||
private fun bindAccountIdJunction(instance: Any?, accountIdKey: String): AccountIdKey {
|
||||
val asStruct = instance.castToStruct()
|
||||
|
||||
return bindAccountId(asStruct[accountIdKey]).intoKey()
|
||||
}
|
||||
|
||||
private fun bindGlobalConsensusJunction(instance: Any?): Junction {
|
||||
val asDictEnum = instance.castToDictEnum()
|
||||
|
||||
return when (asDictEnum.name) {
|
||||
"ByGenesis" -> {
|
||||
val genesis = bindByteArray(asDictEnum.value).toHexString(withPrefix = false)
|
||||
Junction.GlobalConsensus(networkId = NetworkId.Substrate(genesis))
|
||||
}
|
||||
|
||||
"Polkadot" -> Junction.GlobalConsensus(NetworkId.Substrate(Chain.Geneses.POLKADOT))
|
||||
"Kusama" -> Junction.GlobalConsensus(NetworkId.Substrate(Chain.Geneses.KUSAMA))
|
||||
"Westend" -> Junction.GlobalConsensus(NetworkId.Substrate(Chain.Geneses.WESTEND))
|
||||
"Ethereum" -> {
|
||||
val chainId = bindInt(asDictEnum.value.castToStruct()["chain_id"])
|
||||
Junction.GlobalConsensus(NetworkId.Ethereum(chainId))
|
||||
}
|
||||
else -> Junction.Unsupported
|
||||
}
|
||||
}
|
||||
|
||||
// ------ Encode ------
|
||||
|
||||
internal fun RelativeMultiLocation.toEncodableInstanceExt(xcmVersion: XcmVersion) = structOf(
|
||||
"parents" to parents.toBigInteger(),
|
||||
"interior" to interior.toEncodableInstance(xcmVersion)
|
||||
)
|
||||
|
||||
private fun MultiLocation.Interior.toEncodableInstance(xcmVersion: XcmVersion) = when (this) {
|
||||
MultiLocation.Interior.Here -> DictEnum.Entry("Here", null)
|
||||
|
||||
is MultiLocation.Interior.Junctions -> if (junctions.size == 1 && xcmVersion <= XcmVersion.V3) {
|
||||
// X1 is encoded as a single junction in V3 and prior
|
||||
DictEnum.Entry(
|
||||
name = "X1",
|
||||
value = junctions.single().toEncodableInstance(xcmVersion)
|
||||
)
|
||||
} else {
|
||||
DictEnum.Entry(
|
||||
name = "X${junctions.size}",
|
||||
value = junctions.map { it.toEncodableInstance(xcmVersion) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Junction.toEncodableInstance(xcmVersion: XcmVersion) = when (this) {
|
||||
is Junction.GeneralKey -> DictEnum.Entry("GeneralKey", encodableGeneralKey(xcmVersion, key))
|
||||
is Junction.PalletInstance -> DictEnum.Entry("PalletInstance", index)
|
||||
is Junction.ParachainId -> DictEnum.Entry("Parachain", id)
|
||||
is Junction.AccountKey20 -> DictEnum.Entry("AccountKey20", accountId.toJunctionAccountIdInstance(accountIdKey = "key", xcmVersion))
|
||||
is Junction.AccountId32 -> DictEnum.Entry("AccountId32", accountId.toJunctionAccountIdInstance(accountIdKey = "id", xcmVersion))
|
||||
is Junction.GeneralIndex -> DictEnum.Entry("GeneralIndex", index)
|
||||
is Junction.GlobalConsensus -> toEncodableInstance()
|
||||
Junction.Unsupported -> error("Unsupported junction")
|
||||
}
|
||||
|
||||
private fun encodableGeneralKey(xcmVersion: XcmVersion, generalKey: HexString): Any {
|
||||
val bytes = generalKey.fromHex()
|
||||
|
||||
return if (xcmVersion >= XcmVersion.V3) {
|
||||
structOf(
|
||||
"length" to bytes.size.toBigInteger(),
|
||||
"data" to bytes.padEnd(expectedSize = 32, padding = 0)
|
||||
)
|
||||
} else {
|
||||
bytes
|
||||
}
|
||||
}
|
||||
|
||||
private fun Junction.GlobalConsensus.toEncodableInstance(): Any {
|
||||
val innerValue = when (networkId) {
|
||||
is NetworkId.Ethereum -> networkId.toEncodableInstance()
|
||||
is NetworkId.Substrate -> networkId.toEncodableInstance()
|
||||
}
|
||||
|
||||
return DictEnum.Entry("GlobalConsensus", innerValue)
|
||||
}
|
||||
|
||||
private fun NetworkId.Ethereum.toEncodableInstance(): Any {
|
||||
return DictEnum.Entry("Ethereum", structOf("chain_id" to chainId.toBigInteger()))
|
||||
}
|
||||
|
||||
private fun NetworkId.Substrate.toEncodableInstance(): Any {
|
||||
return when (genesisHash) {
|
||||
Chain.Geneses.POLKADOT -> DictEnum.Entry("Polkadot", null)
|
||||
Chain.Geneses.KUSAMA -> DictEnum.Entry("Kusama", null)
|
||||
Chain.Geneses.WESTEND -> DictEnum.Entry("Westend", null)
|
||||
Chain.Ids.ETHEREUM -> DictEnum.Entry("Ethereum", null)
|
||||
else -> DictEnum.Entry("ByGenesis", genesisHash.fromHex())
|
||||
}
|
||||
}
|
||||
|
||||
private fun AccountIdKey.toJunctionAccountIdInstance(accountIdKey: String, xcmVersion: XcmVersion) = structOf(
|
||||
"network" to emptyNetworkField(xcmVersion),
|
||||
accountIdKey to value
|
||||
)
|
||||
|
||||
private fun emptyNetworkField(xcmVersion: XcmVersion): Any? {
|
||||
return if (xcmVersion >= XcmVersion.V3) {
|
||||
// Network in V3+ is encoded as Option<NetworkId>
|
||||
null
|
||||
} else {
|
||||
// Network in V2- is encoded as Enum with Any variant
|
||||
DictEnum.Entry("Any", null)
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.multiLocation
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedToDynamicScaleInstance
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
|
||||
data class RelativeMultiLocation(
|
||||
val parents: Int,
|
||||
override val interior: Interior
|
||||
) : MultiLocation(interior), VersionedToDynamicScaleInstance {
|
||||
|
||||
override fun toEncodableInstance(xcmVersion: XcmVersion): Any {
|
||||
return toEncodableInstanceExt(xcmVersion)
|
||||
}
|
||||
}
|
||||
|
||||
fun RelativeMultiLocation.isHere(): Boolean {
|
||||
return parents == 0 && interior.isHere()
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi
|
||||
|
||||
import android.util.Log
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ScaleResult
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ScaleResultError
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.toResult
|
||||
|
||||
fun <T> Result<ScaleResult<T, *>>.getInnerSuccessOrThrow(errorLogTag: String?): T {
|
||||
return getOrThrow()
|
||||
.toResult()
|
||||
.onFailure {
|
||||
Log.e(errorLogTag, "Xcm api call failed: ${(it as ScaleResultError).content}")
|
||||
}.getOrThrow()
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ScaleResult
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedRawXcmMessage
|
||||
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.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall
|
||||
|
||||
interface DryRunApi {
|
||||
|
||||
suspend fun dryRunXcm(
|
||||
originLocation: VersionedXcmLocation,
|
||||
xcm: VersionedRawXcmMessage,
|
||||
chainId: ChainId
|
||||
): Result<ScaleResult<XcmDryRunEffects, DryRunEffectsResultErr>>
|
||||
|
||||
suspend fun dryRunCall(
|
||||
originCaller: OriginCaller,
|
||||
call: GenericCall.Instance,
|
||||
xcmResultsVersion: XcmVersion,
|
||||
chainId: ChainId
|
||||
): Result<ScaleResult<CallDryRunEffects, DryRunEffectsResultErr>>
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.ScaleResult
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindEvent
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindList
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.utils.RuntimeContext
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedRawXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.message.bindVersionedRawXcmMessage
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericEvent
|
||||
|
||||
class CallDryRunEffects(
|
||||
val executionResult: ScaleResult<DispatchPostInfo, DispatchErrorWithPostInfo>,
|
||||
override val emittedEvents: List<GenericEvent.Instance>,
|
||||
// We don't need to fully decode/encode intermediate xcm messages
|
||||
val localXcm: VersionedRawXcmMessage?,
|
||||
override val forwardedXcms: ForwardedXcms
|
||||
) : DryRunEffects {
|
||||
|
||||
companion object {
|
||||
|
||||
context(RuntimeContext)
|
||||
fun bind(decodedInstance: Any?): CallDryRunEffects {
|
||||
val asStruct = decodedInstance.castToStruct()
|
||||
return CallDryRunEffects(
|
||||
executionResult = ScaleResult.bind(
|
||||
dynamicInstance = asStruct["executionResult"],
|
||||
bindOk = DispatchPostInfo::bind,
|
||||
bindError = { DispatchErrorWithPostInfo.bind(it) }
|
||||
),
|
||||
emittedEvents = bindList(asStruct["emittedEvents"], ::bindEvent),
|
||||
localXcm = asStruct.get<Any?>("localXcm")?.let { bindVersionedRawXcmMessage(it) },
|
||||
forwardedXcms = bindForwardedXcms(asStruct["forwardedXcms"])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.DispatchError
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindDispatchError
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.utils.RuntimeContext
|
||||
|
||||
class DispatchErrorWithPostInfo(
|
||||
val postInfo: DispatchPostInfo,
|
||||
val error: DispatchError
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
context(RuntimeContext)
|
||||
fun bind(decodedInstance: Any?): DispatchErrorWithPostInfo {
|
||||
val asStruct = decodedInstance.castToStruct()
|
||||
|
||||
return DispatchErrorWithPostInfo(
|
||||
postInfo = DispatchPostInfo.bind(asStruct["post_info"]),
|
||||
error = bindDispatchError(asStruct["error"])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
// Info not needed yet
|
||||
object DispatchPostInfo {
|
||||
|
||||
fun bind(decodedInstance: Any?): DispatchPostInfo {
|
||||
return this
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericEvent
|
||||
|
||||
interface DryRunEffects {
|
||||
|
||||
val emittedEvents: List<GenericEvent.Instance>
|
||||
|
||||
val forwardedXcms: ForwardedXcms
|
||||
}
|
||||
|
||||
fun DryRunEffects.senderXcmVersion(): XcmVersion {
|
||||
// For referencing destination, dry run uses sender's xcm version
|
||||
val (destination) = forwardedXcms.first()
|
||||
return destination.version
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
// Info not needed yet
|
||||
object DryRunEffectsResultErr {
|
||||
|
||||
fun bind(decodedInstance: Any?): DryRunEffectsResultErr {
|
||||
return this
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindList
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToList
|
||||
import io.novafoundation.nova.feature_xcm_api.message.VersionedRawXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.message.bindVersionedRawXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.bindVersionedMultiLocation
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.VersionedXcmLocation
|
||||
|
||||
typealias ForwardedXcms = List<Pair<VersionedXcmLocation, List<VersionedRawXcmMessage>>>
|
||||
|
||||
fun bindForwardedXcms(decodedInstance: Any?): ForwardedXcms {
|
||||
return bindList(decodedInstance) { inner ->
|
||||
val (locationRaw, messagesRaw) = inner.castToList()
|
||||
val messages = bindList(messagesRaw, ::bindVersionedRawXcmMessage)
|
||||
val location = bindVersionedMultiLocation(locationRaw)
|
||||
|
||||
location to messages
|
||||
}
|
||||
}
|
||||
fun ForwardedXcms.getByLocation(location: VersionedXcmLocation): List<VersionedRawXcmMessage> {
|
||||
return find { it.first == location }?.second.orEmpty()
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
import io.novafoundation.nova.common.address.AccountIdKey
|
||||
import io.novafoundation.nova.common.utils.scale.ToDynamicScaleInstance
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
|
||||
sealed class OriginCaller : ToDynamicScaleInstance {
|
||||
|
||||
sealed class System : OriginCaller() {
|
||||
|
||||
object Root : System() {
|
||||
|
||||
override fun toEncodableInstance(): Any? {
|
||||
return wrapInSystemDict(DictEnum.Entry("Root", null))
|
||||
}
|
||||
}
|
||||
|
||||
class Signed(val accountId: AccountIdKey) : System() {
|
||||
|
||||
override fun toEncodableInstance(): Any? {
|
||||
return wrapInSystemDict(DictEnum.Entry("Signed", accountId.value))
|
||||
}
|
||||
}
|
||||
|
||||
protected fun wrapInSystemDict(inner: Any): DictEnum.Entry<*> {
|
||||
return DictEnum.Entry("system", inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindEvent
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindList
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.utils.RuntimeContext
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericEvent
|
||||
|
||||
class XcmDryRunEffects(
|
||||
val executionResult: XcmOutcome,
|
||||
override val emittedEvents: List<GenericEvent.Instance>,
|
||||
override val forwardedXcms: ForwardedXcms
|
||||
) : DryRunEffects {
|
||||
|
||||
companion object {
|
||||
|
||||
context(RuntimeContext)
|
||||
fun bind(decodedInstance: Any?): XcmDryRunEffects {
|
||||
val asStruct = decodedInstance.castToStruct()
|
||||
return XcmDryRunEffects(
|
||||
executionResult = XcmOutcome.bind(asStruct["executionResult"]),
|
||||
emittedEvents = bindList(asStruct["emittedEvents"], ::bindEvent),
|
||||
forwardedXcms = bindForwardedXcms(asStruct["forwardedXcms"])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.dryRun.model
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.Weight
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.bindWeight
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.incompatible
|
||||
import io.novafoundation.nova.common.utils.scale.DynamicScaleInstance
|
||||
|
||||
sealed class XcmOutcome {
|
||||
|
||||
companion object {
|
||||
|
||||
fun bind(decodedInstance: Any?): XcmOutcome {
|
||||
val asEnum = decodedInstance.castToDictEnum()
|
||||
val value = asEnum.value.castToStruct()
|
||||
|
||||
return when (asEnum.name) {
|
||||
"Complete" -> Complete(
|
||||
used = bindWeight(value["used"])
|
||||
)
|
||||
|
||||
"Incomplete" -> Incomplete(
|
||||
used = bindWeight(value["used"]),
|
||||
error = DynamicScaleInstance(value["error"])
|
||||
)
|
||||
|
||||
"Error" -> Error(
|
||||
error = DynamicScaleInstance(value["error"])
|
||||
)
|
||||
|
||||
else -> incompatible()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Complete(val used: Weight) : XcmOutcome()
|
||||
|
||||
class Incomplete(val used: Weight, val error: DynamicScaleInstance) : XcmOutcome()
|
||||
|
||||
class Error(val error: DynamicScaleInstance) : XcmOutcome()
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.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.feature_xcm_api.message.VersionedXcmMessage
|
||||
import io.novafoundation.nova.feature_xcm_api.runtimeApi.xcmPayment.model.QueryXcmWeightErr
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
|
||||
interface XcmPaymentApi {
|
||||
|
||||
suspend fun queryXcmWeight(
|
||||
chainId: ChainId,
|
||||
xcm: VersionedXcmMessage,
|
||||
): Result<ScaleResult<WeightV2, QueryXcmWeightErr>>
|
||||
|
||||
suspend fun isSupported(chainId: ChainId): Boolean
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.runtimeApi.xcmPayment.model
|
||||
|
||||
// Info not needed yet
|
||||
object QueryXcmWeightErr {
|
||||
|
||||
fun bind(decodedInstance: Any?): QueryXcmWeightErr {
|
||||
return this
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.versions
|
||||
|
||||
interface VersionedToDynamicScaleInstance {
|
||||
|
||||
fun toEncodableInstance(xcmVersion: XcmVersion): Any?
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.versions
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
|
||||
import io.novafoundation.nova.common.utils.scale.DynamicScaleInstance
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.RelativeMultiLocation
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
|
||||
data class VersionedXcm<T>(
|
||||
val xcm: T,
|
||||
val version: XcmVersion
|
||||
)
|
||||
|
||||
@JvmName("toEncodableInstanceVersioned")
|
||||
fun VersionedXcm<out VersionedToDynamicScaleInstance>.toEncodableInstance(): DictEnum.Entry<*> {
|
||||
return DictEnum.Entry(
|
||||
name = version.enumerationKey(),
|
||||
value = xcm.toEncodableInstance(version)
|
||||
)
|
||||
}
|
||||
|
||||
fun VersionedXcm<out DynamicScaleInstance>.toEncodableInstance(): DictEnum.Entry<*> {
|
||||
return DictEnum.Entry(
|
||||
name = version.enumerationKey(),
|
||||
value = xcm.toEncodableInstance()
|
||||
)
|
||||
}
|
||||
|
||||
fun <T> bindVersionedXcm(instance: Any?, inner: (Any?, xcmVersion: XcmVersion) -> T): VersionedXcm<T> {
|
||||
val versionEnum = instance.castToDictEnum()
|
||||
val xcmVersion = XcmVersion.fromEnumerationKey(versionEnum.name)
|
||||
|
||||
return VersionedXcm(inner(versionEnum.value, xcmVersion), xcmVersion)
|
||||
}
|
||||
|
||||
fun <T> T.versionedXcm(xcmVersion: XcmVersion): VersionedXcm<T> {
|
||||
return VersionedXcm(this, xcmVersion)
|
||||
}
|
||||
|
||||
typealias VersionedXcmLocation = VersionedXcm<RelativeMultiLocation>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.versions
|
||||
|
||||
enum class XcmVersion(val version: Int) {
|
||||
V0(0), V1(1), V2(2), V3(3), V4(4), V5(5);
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromVersion(version: Int): XcmVersion {
|
||||
return values().find { it.version == version }
|
||||
?: error("Unknown xcm version: $version")
|
||||
}
|
||||
|
||||
val GLOBAL_DEFAULT = V2
|
||||
}
|
||||
}
|
||||
|
||||
// Return xcm version from a enumeration key in form of "V{version}"
|
||||
fun XcmVersion.Companion.fromEnumerationKey(enumerationKey: String): XcmVersion {
|
||||
val withoutPrefix = enumerationKey.removePrefix("V")
|
||||
val version = withoutPrefix.toInt()
|
||||
return fromVersion(version)
|
||||
}
|
||||
|
||||
fun XcmVersion?.orDefault(): XcmVersion {
|
||||
return this ?: XcmVersion.GLOBAL_DEFAULT
|
||||
}
|
||||
|
||||
fun XcmVersion.enumerationKey(): String {
|
||||
return "V$version"
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.versions.detector
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.versions.XcmVersion
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.RuntimeType
|
||||
|
||||
interface XcmVersionDetector {
|
||||
|
||||
suspend fun lowestPresentMultiLocationVersion(chainId: ChainId): XcmVersion?
|
||||
|
||||
suspend fun lowestPresentMultiAssetsVersion(chainId: ChainId): XcmVersion?
|
||||
|
||||
suspend fun lowestPresentMultiAssetIdVersion(chainId: ChainId): XcmVersion?
|
||||
|
||||
suspend fun lowestPresentMultiAssetVersion(chainId: ChainId): XcmVersion?
|
||||
|
||||
suspend fun detectMultiLocationVersion(chainId: ChainId, multiLocationType: RuntimeType<*, *>?): XcmVersion?
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.weight
|
||||
|
||||
import io.novafoundation.nova.common.data.network.runtime.binding.WeightV2
|
||||
import io.novafoundation.nova.common.utils.scale.ToDynamicScaleInstance
|
||||
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
|
||||
|
||||
sealed class WeightLimit : ToDynamicScaleInstance {
|
||||
|
||||
companion object {
|
||||
|
||||
fun zero(): WeightLimit {
|
||||
return Limited(WeightV2.zero())
|
||||
}
|
||||
}
|
||||
|
||||
object Unlimited : WeightLimit() {
|
||||
|
||||
override fun toEncodableInstance(): Any? {
|
||||
return DictEnum.Entry("Unlimited", null)
|
||||
}
|
||||
}
|
||||
|
||||
class Limited(val weightV2: WeightV2) : WeightLimit() {
|
||||
|
||||
override fun toEncodableInstance(): Any? {
|
||||
return DictEnum.Entry("Limited", weightV2.toEncodableInstance())
|
||||
}
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package io.novafoundation.nova.feature_xcm_api.multiLocation
|
||||
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Interior
|
||||
import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction.ParachainId
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
|
||||
class AbsoluteMultiLocationTest {
|
||||
|
||||
@Test
|
||||
fun `reanchor global pov should remain unchanged`() {
|
||||
val initial = AbsoluteMultiLocation(ParachainId(1000))
|
||||
val pov = AbsoluteMultiLocation(Interior.Here)
|
||||
val expected = initial.toRelative()
|
||||
|
||||
val result = initial.fromPointOfViewOf(pov)
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reanchor no common junctions`() {
|
||||
val initial = AbsoluteMultiLocation(ParachainId(1000))
|
||||
val pov = AbsoluteMultiLocation(ParachainId(2000))
|
||||
val expected = RelativeMultiLocation(parents=1, interior = Junctions(ParachainId(1000)))
|
||||
|
||||
val result = initial.fromPointOfViewOf(pov)
|
||||
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reanchor one common junction`() {
|
||||
val initial = AbsoluteMultiLocation(ParachainId(1000), ParachainId(2000))
|
||||
val pov = AbsoluteMultiLocation(ParachainId(1000), ParachainId(3000))
|
||||
val expected = RelativeMultiLocation(parents=1, interior = Junctions(ParachainId(2000)))
|
||||
|
||||
val result = initial.fromPointOfViewOf(pov)
|
||||
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reanchor all common junction`() {
|
||||
val initial = AbsoluteMultiLocation(ParachainId(1000), ParachainId(2000))
|
||||
val pov = AbsoluteMultiLocation(ParachainId(1000), ParachainId(2000))
|
||||
val expected = RelativeMultiLocation(parents=0, interior = Interior.Here)
|
||||
|
||||
val result = initial.fromPointOfViewOf(pov)
|
||||
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reanchor global to global`() {
|
||||
val initial = AbsoluteMultiLocation(Interior.Here)
|
||||
val pov = AbsoluteMultiLocation(Interior.Here)
|
||||
val expected = initial.toRelative()
|
||||
|
||||
val result = initial.fromPointOfViewOf(pov)
|
||||
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reanchor pov is successor of initial`() {
|
||||
val initial = AbsoluteMultiLocation(Interior.Here)
|
||||
val pov = AbsoluteMultiLocation(ParachainId(1000))
|
||||
val expected = RelativeMultiLocation(parents=1, interior = Interior.Here)
|
||||
|
||||
val result = initial.fromPointOfViewOf(pov)
|
||||
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reanchor initial is successor of pov`() {
|
||||
val initial = AbsoluteMultiLocation(ParachainId(1000), ParachainId(2000))
|
||||
val pov = AbsoluteMultiLocation(ParachainId(1000))
|
||||
val expected = RelativeMultiLocation(parents=0, interior = Junctions(ParachainId(2000)))
|
||||
|
||||
val result = initial.fromPointOfViewOf(pov)
|
||||
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user