diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/SubstrateSdkExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/SubstrateSdkExt.kt index 3a5efd5..c756602 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/SubstrateSdkExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/SubstrateSdkExt.kt @@ -301,7 +301,7 @@ fun RuntimeMetadata.identity() = module(Modules.IDENTITY) fun RuntimeMetadata.automationTime() = module(Modules.AUTOMATION_TIME) -fun RuntimeMetadata.parachainInfoOrNull() = moduleOrNull(Modules.PARACHAIN_INFO) +fun RuntimeMetadata.parachainInfoOrNull() = firstExistingModuleOrNull(Modules.PARACHAIN_INFO, Modules.TEYRCHAIN_INFO) fun RuntimeMetadata.parasOrNull() = moduleOrNull(Modules.PARAS) fun RuntimeMetadata.referenda() = module(Modules.REFERENDA) @@ -382,7 +382,9 @@ fun Module.firstExistingCallName(vararg options: String): String { return options.first(::hasCall) } -fun RuntimeMetadata.xcmPalletName() = firstExistingModuleName("XcmPallet", "PolkadotXcm") +fun RuntimeMetadata.xcmPalletName() = firstExistingModuleName("XcmPallet", "PolkadotXcm", "PezkuwiXcm") + +fun RuntimeMetadata.xcmPalletNameOrNull(): String? = firstExistingModuleOrNull("XcmPallet", "PolkadotXcm", "PezkuwiXcm")?.name fun RuntimeMetadata.xTokensName() = firstExistingModuleName("XTokens", "Xtokens") @@ -612,6 +614,7 @@ object Modules { const val IDENTITY = "Identity" const val PARACHAIN_INFO = "ParachainInfo" + const val TEYRCHAIN_INFO = "TeyrchainInfo" const val PARAS = "Paras" const val AUTOMATION_TIME = "AutomationTime" diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/repository/ParachainInfoExt.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/repository/ParachainInfoExt.kt index 1654d5c..fc8de0b 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/repository/ParachainInfoExt.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/repository/ParachainInfoExt.kt @@ -3,16 +3,30 @@ package io.novafoundation.nova.feature_wallet_api.data.repository import io.novafoundation.nova.feature_xcm_api.chain.XcmChain 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.MultiLocation.Junction.ParachainId.Companion.JUNCTION_TYPE_PARACHAIN +import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction.ParachainId.Companion.JUNCTION_TYPE_TEYRCHAIN import io.novafoundation.nova.feature_xcm_api.multiLocation.chainLocation import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.repository.ParachainInfoRepository +// Pezkuwi chain IDs - these chains use "Teyrchain" instead of "Parachain" in XCM +private val PEZKUWI_CHAIN_IDS = setOf( + "bb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75", // PEZKUWI + "00d0e1d0581c3cd5c5768652d52f4520184018b44f56a2ae1e0dc9d65c00c948", // PEZKUWI_ASSET_HUB + "58269e9c184f721e0309332d90cafc410df1519a5dc27a5fd9b3bf5fd2d129f8" // PEZKUWI_PEOPLE +) + +private fun junctionTypeNameForChain(chainId: ChainId): String { + return if (chainId in PEZKUWI_CHAIN_IDS) JUNCTION_TYPE_TEYRCHAIN else JUNCTION_TYPE_PARACHAIN +} + suspend fun ParachainInfoRepository.getXcmChain(chain: Chain): XcmChain { return XcmChain(paraId(chain.id), chain) } suspend fun ParachainInfoRepository.getChainLocation(chainId: ChainId): ChainLocation { - val location = AbsoluteMultiLocation.chainLocation(paraId(chainId)) + val junctionTypeName = junctionTypeNameForChain(chainId) + val location = AbsoluteMultiLocation.chainLocation(paraId(chainId), junctionTypeName) return ChainLocation(chainId, location) } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/common/TransferAssetUsingTypeTransactor.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/common/TransferAssetUsingTypeTransactor.kt index b8f8c30..3006ba3 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/common/TransferAssetUsingTypeTransactor.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/common/TransferAssetUsingTypeTransactor.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.crosschain.common +import android.util.Log import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.utils.composeCall import io.novafoundation.nova.common.utils.metadata @@ -54,6 +55,19 @@ class TransferAssetUsingTypeTransactor @Inject constructor( val transferTypeParam = configuration.transferTypeParam(multiAssetsVersion) + // Debug logging for XCM transfer + val destLocation = configuration.destinationChainLocationOnOrigin() + Log.d("XCM_TRANSFER", "=== XCM TRANSFER DEBUG ===") + Log.d("XCM_TRANSFER", "Origin chain: ${configuration.originChain.chain.name} (${configuration.originChain.chain.id})") + Log.d("XCM_TRANSFER", "Origin parachainId: ${configuration.originChain.parachainId}") + Log.d("XCM_TRANSFER", "Destination chain: ${configuration.destinationChain.chain.name} (${configuration.destinationChain.chain.id})") + Log.d("XCM_TRANSFER", "Destination parachainId: ${configuration.destinationChain.parachainId}") + Log.d("XCM_TRANSFER", "Destination location (relative): parents=${destLocation.parents}, interior=${destLocation.interior}") + Log.d("XCM_TRANSFER", "Destination junctions: ${destLocation.interior}") + Log.d("XCM_TRANSFER", "Transfer type: ${configuration.transferType}") + Log.d("XCM_TRANSFER", "XCM Version: $multiLocationVersion") + Log.d("XCM_TRANSFER", "==========================") + return chainRegistry.withRuntime(configuration.originChainId) { composeCall( moduleName = metadata.xcmPalletName(), diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainTransactor.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainTransactor.kt index 300abbf..b7d0297 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainTransactor.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainTransactor.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.crosschain.dynamic +import android.util.Log import io.novafoundation.nova.common.address.AccountIdKey import io.novafoundation.nova.common.data.network.runtime.binding.WeightV2 import io.novafoundation.nova.common.di.scope.FeatureScope @@ -130,6 +131,16 @@ class DynamicCrossChainTransactor @Inject constructor( val totalTransferAmount = transfer.amountPlanks + crossChainFee val assetAbsoluteMultiLocation = configuration.transferType.assetAbsoluteLocation + // Debug logging for Dynamic XCM transfer + Log.d("XCM_DYNAMIC", "=== DYNAMIC XCM TRANSFER DEBUG ===") + Log.d("XCM_DYNAMIC", "Origin chain: ${configuration.originChain.chain.name} (${configuration.originChain.chain.id})") + Log.d("XCM_DYNAMIC", "Origin parachainId: ${configuration.originChain.parachainId}") + Log.d("XCM_DYNAMIC", "Destination chain: ${configuration.destinationChain.chain.name} (${configuration.destinationChain.chain.id})") + Log.d("XCM_DYNAMIC", "Destination parachainId: ${configuration.destinationChain.parachainId}") + Log.d("XCM_DYNAMIC", "Destination location: ${configuration.destinationChainLocation}") + Log.d("XCM_DYNAMIC", "Transfer type: ${configuration.transferType}") + Log.d("XCM_DYNAMIC", "================================") + when (val transferType = configuration.transferType) { is XcmTransferType.Teleport -> buildTeleportProgram( assetLocation = assetAbsoluteMultiLocation, diff --git a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/chain/XcmChain.kt b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/chain/XcmChain.kt index 3f5d3be..8ab7da8 100644 --- a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/chain/XcmChain.kt +++ b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/chain/XcmChain.kt @@ -2,17 +2,27 @@ 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.MultiLocation.Junction.ParachainId.Companion.JUNCTION_TYPE_PARACHAIN +import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction.ParachainId.Companion.JUNCTION_TYPE_TEYRCHAIN import io.novafoundation.nova.feature_xcm_api.multiLocation.chainLocation import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigInteger +// Pezkuwi chain IDs - these chains use "Teyrchain" instead of "Parachain" in XCM +private val PEZKUWI_CHAIN_IDS = setOf( + "bb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75", // PEZKUWI + "00d0e1d0581c3cd5c5768652d52f4520184018b44f56a2ae1e0dc9d65c00c948", // PEZKUWI_ASSET_HUB + "58269e9c184f721e0309332d90cafc410df1519a5dc27a5fd9b3bf5fd2d129f8" // PEZKUWI_PEOPLE +) + class XcmChain( val parachainId: BigInteger?, val chain: Chain ) fun XcmChain.absoluteLocation(): AbsoluteMultiLocation { - return AbsoluteMultiLocation.chainLocation(parachainId) + val junctionTypeName = if (chain.id in PEZKUWI_CHAIN_IDS) JUNCTION_TYPE_TEYRCHAIN else JUNCTION_TYPE_PARACHAIN + return AbsoluteMultiLocation.chainLocation(parachainId, junctionTypeName) } fun XcmChain.isRelay(): Boolean { diff --git a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/AbsoluteLocation.kt b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/AbsoluteLocation.kt index a09f37a..54a3d08 100644 --- a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/AbsoluteLocation.kt +++ b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/AbsoluteLocation.kt @@ -41,6 +41,9 @@ class AbsoluteMultiLocation( } } -fun AbsoluteMultiLocation.Companion.chainLocation(parachainId: ParaId?): AbsoluteMultiLocation { - return listOfNotNull(parachainId?.let(MultiLocation.Junction::ParachainId)).asLocation() +fun AbsoluteMultiLocation.Companion.chainLocation( + parachainId: ParaId?, + junctionTypeName: String = MultiLocation.Junction.ParachainId.JUNCTION_TYPE_PARACHAIN +): AbsoluteMultiLocation { + return listOfNotNull(parachainId?.let { MultiLocation.Junction.ParachainId(it, junctionTypeName) }).asLocation() } diff --git a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocation.kt b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocation.kt index 197a794..afdaa97 100644 --- a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocation.kt +++ b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocation.kt @@ -37,9 +37,17 @@ abstract class MultiLocation( sealed class Junction { - data class ParachainId(val id: ParaId) : Junction() { + data class ParachainId( + val id: ParaId, + val junctionTypeName: String = JUNCTION_TYPE_PARACHAIN + ) : Junction() { - constructor(id: Int) : this(id.toBigInteger()) + constructor(id: Int, junctionTypeName: String = JUNCTION_TYPE_PARACHAIN) : this(id.toBigInteger(), junctionTypeName) + + companion object { + const val JUNCTION_TYPE_PARACHAIN = "Parachain" + const val JUNCTION_TYPE_TEYRCHAIN = "Teyrchain" + } } data class GeneralKey(val key: HexString) : Junction() diff --git a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncoding.kt b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncoding.kt index dff6a6b..4d32399 100644 --- a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncoding.kt +++ b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/multiLocation/MultiLocationEncoding.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_xcm_api.multiLocation +import android.util.Log import io.novafoundation.nova.common.address.AccountIdKey import io.novafoundation.nova.common.address.intoKey import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId @@ -69,7 +70,8 @@ private fun bindJunction(instance: Any?): Junction { return when (asDictEnum.name) { "GeneralKey" -> Junction.GeneralKey(bindGeneralKey(asDictEnum.value)) "PalletInstance" -> Junction.PalletInstance(bindNumber(asDictEnum.value)) - "Parachain" -> Junction.ParachainId(bindNumber(asDictEnum.value)) + // Accept both "Parachain" (Polkadot ecosystem) and "Teyrchain" (Pezkuwi ecosystem) + "Parachain", "Teyrchain" -> Junction.ParachainId(bindNumber(asDictEnum.value), asDictEnum.name) "GeneralIndex" -> Junction.GeneralIndex(bindNumber(asDictEnum.value)) "GlobalConsensus" -> bindGlobalConsensusJunction(asDictEnum.value) "AccountKey20" -> Junction.AccountKey20(bindAccountIdJunction(asDictEnum.value, accountIdKey = "key")) @@ -146,7 +148,10 @@ private fun MultiLocation.Interior.toEncodableInstance(xcmVersion: 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.ParachainId -> { + Log.d("XCM_ENCODE", "Encoding ParachainId: id=$id, junctionTypeName=$junctionTypeName") + DictEnum.Entry(junctionTypeName, 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) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/repository/ParachainInfoRepository.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/repository/ParachainInfoRepository.kt index c8e3caa..06c997e 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/repository/ParachainInfoRepository.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/repository/ParachainInfoRepository.kt @@ -2,10 +2,12 @@ package io.novafoundation.nova.runtime.repository import io.novafoundation.nova.common.data.network.runtime.binding.ParaId import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber -import io.novafoundation.nova.common.utils.parachainInfoOrNull +import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource -import io.novasama.substrate_sdk_android.runtime.metadata.storage +import io.novasama.substrate_sdk_android.runtime.metadata.module.Module +import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull +import io.novasama.substrate_sdk_android.runtime.metadata.storageOrNull import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -26,7 +28,13 @@ internal class RealParachainInfoRepository( paraIdCache.getValue(chainId) } else { remoteStorageSource.query(chainId) { - runtime.metadata.parachainInfoOrNull()?.storage("ParachainId")?.query(binding = ::bindNumber) + // Try Polkadot-style first (ParachainInfo.ParachainId) + // Then try Pezkuwi-style (TeyrchainInfo.TeyrchainId) + val polkadotModule = runtime.metadata.moduleOrNull(Modules.PARACHAIN_INFO) + val pezkuwiModule = runtime.metadata.moduleOrNull(Modules.TEYRCHAIN_INFO) + + polkadotModule?.storageOrNull("ParachainId")?.query(binding = ::bindNumber) + ?: pezkuwiModule?.storageOrNull("TeyrchainId")?.query(binding = ::bindNumber) } .also { paraIdCache[chainId] = it } }