diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/swap/RealSwapService.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/swap/RealSwapService.kt index fcbc8d0..4560aec 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/swap/RealSwapService.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/swap/RealSwapService.kt @@ -135,6 +135,12 @@ private const val SHARED_SUBSCRIPTIONS = "RealSwapService.SharedSubscriptions" private val ADDITIONAL_ESTIMATE_BUFFER = 3.seconds +private val PEZKUWI_CHAIN_IDS = setOf( + Chain.Geneses.PEZKUWI, + Chain.Geneses.PEZKUWI_ASSET_HUB, + Chain.Geneses.PEZKUWI_PEOPLE +) + internal class RealSwapService( private val assetConversionFactory: AssetConversionExchangeFactory, private val hydraDxExchangeFactory: HydraDxExchangeFactory, @@ -155,19 +161,23 @@ internal class RealSwapService( override suspend fun warmUpCommonChains(computationScope: CoroutineScope): Result { return runCatching { withContext(Dispatchers.Default) { - warmUpChain(Chain.Geneses.HYDRA_DX, computationScope) - warmUpChain(Chain.Geneses.POLKADOT_ASSET_HUB, computationScope) + // Warm up each chain independently - failures shouldn't affect other chains + warmUpChainSafely(Chain.Geneses.HYDRA_DX, computationScope) + warmUpChainSafely(Chain.Geneses.POLKADOT_ASSET_HUB, computationScope) + warmUpChainSafely(Chain.Geneses.PEZKUWI_ASSET_HUB, computationScope) } } } - private suspend fun warmUpChain(chainId: ChainId, computationScope: CoroutineScope) { - nodeVisitFilter(computationScope).warmUpChain(chainId) + private suspend fun warmUpChainSafely(chainId: ChainId, computationScope: CoroutineScope) { + try { + nodeVisitFilter(computationScope).warmUpChain(chainId) + } catch (e: Exception) { + Log.w("SwapService", "Failed to warm up chain $chainId: ${e.message}") + } } override suspend fun sync(coroutineScope: CoroutineScope) { - Log.d("Swaps", "Syncing swap service") - exchangeRegistry(coroutineScope) .allExchanges() .forEachAsync { it.sync() } @@ -250,7 +260,7 @@ internal class RealSwapService( val actualSwapLimit = operation.estimatedSwapLimit.replaceAmountIn(newAmountIn, shouldReplaceBuyWithSell) val segmentSubmissionArgs = AtomicSwapOperationSubmissionArgs(actualSwapLimit) - Log.d("SwapSubmission", "$displayData with $actualSwapLimit") + if (debug) Log.d("SwapSubmission", "$displayData with $actualSwapLimit") operation.execute(segmentSubmissionArgs).onFailure { Log.e("SwapSubmission", "Swap failed on stage '$displayData'", it) @@ -621,7 +631,7 @@ internal class RealSwapService( override suspend fun roughlyEstimateFee(path: Path>): PathRoughFeeEstimation { // USDT is used to determine usd to selected currency rate without making a separate request to price api - val usdtOnAssetHub = chainRegistry.getUSDTOnAssetHub() ?: return PathRoughFeeEstimation.zero() + val usdtOnAssetHub = chainRegistry.getUSDTOnAssetHub(path) ?: return PathRoughFeeEstimation.zero() val operationPrototypes = path.constructAtomicOperationPrototypes() @@ -639,9 +649,26 @@ internal class RealSwapService( ) } - private suspend fun ChainRegistry.getUSDTOnAssetHub(): Chain.Asset? { - val assetHub = getChain(Chain.Geneses.POLKADOT_ASSET_HUB) - return assetHub.assets.find { it.symbol.value == "USDT" } + private suspend fun ChainRegistry.getUSDTOnAssetHub(path: Path>): Chain.Asset? { + // Determine which ecosystem the swap is in based on the path + val involvesPezkuwi = path.any { edge -> + edge.edge.from.chainId in PEZKUWI_CHAIN_IDS || edge.edge.to.chainId in PEZKUWI_CHAIN_IDS + } + + val assetHubGenesis = if (involvesPezkuwi) { + Chain.Geneses.PEZKUWI_ASSET_HUB + } else { + Chain.Geneses.POLKADOT_ASSET_HUB + } + + return try { + val assetHub = getChain(assetHubGenesis) + assetHub.assets.find { it.symbol.value == "USDT" || it.symbol.value == "wUSDT" } + } catch (e: Exception) { + // Fallback to Polkadot Asset Hub if Pezkuwi Asset Hub is not available + val assetHub = getChain(Chain.Geneses.POLKADOT_ASSET_HUB) + assetHub.assets.find { it.symbol.value == "USDT" } + } } private fun Map.fiatToPlanks(fiat: BigDecimal, chainAsset: Chain.Asset): Balance { @@ -734,6 +761,8 @@ internal class RealSwapService( } private fun logFee(fee: SwapFee) { + if (!debug) return + val route = fee.segments.joinToString(separator = "\n") { segment -> val allFees = buildList { add(segment.fee.submissionFee) @@ -750,6 +779,8 @@ internal class RealSwapService( } private suspend fun logQuotes(quotedTrades: List) { + if (!debug) return + val allCandidates = quotedTrades.sortedDescending() .map { trade -> formatTrade(trade) } .joinToString(separator = "\n") diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/xcm/legacy/LegacyCrossChainTransfersConfigurationExt.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/xcm/legacy/LegacyCrossChainTransfersConfigurationExt.kt index c78dc8e..9a4b30f 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/xcm/legacy/LegacyCrossChainTransfersConfigurationExt.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/xcm/legacy/LegacyCrossChainTransfersConfigurationExt.kt @@ -8,6 +8,8 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.xcm.legacy.LegacyC import io.novafoundation.nova.feature_xcm_api.chain.XcmChain import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Interior import io.novafoundation.nova.feature_xcm_api.multiLocation.MultiLocation.Junction +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.RelativeMultiLocation import io.novafoundation.nova.feature_xcm_api.multiLocation.toInterior import io.novafoundation.nova.runtime.ext.fullId @@ -18,6 +20,17 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.FullChainAssetId import java.math.BigInteger import io.novafoundation.nova.feature_wallet_api.domain.model.xcm.dynamic.reserve.XcmTransferType as XcmReserveTransferType +// 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 +} + fun LegacyCrossChainTransfersConfiguration.XcmFee.Mode.Proportional.weightToFee(weight: Weight): BigInteger { val pico = BigInteger.TEN.pow(12) @@ -98,7 +111,7 @@ suspend fun LegacyCrossChainTransfersConfiguration.transferConfiguration( ), assetLocation = originAssetLocationOf(assetTransfers), reserveChainLocation = reserveAssetLocation.multiLocation, - destinationChainLocation = destinationLocation(originChain, destinationXcmChain.parachainId), + destinationChainLocation = destinationLocation(originChain, destinationXcmChain.parachainId, destinationChain.id), destinationFee = destinationFee, reserveFee = reserveFee, originChainAsset = originAsset, @@ -133,25 +146,27 @@ private fun LegacyCrossChainTransfersConfiguration.matchInstructions( private fun destinationLocation( originChain: Chain, - destinationParaId: ParaId? + destinationParaId: ParaId?, + destinationChainId: ChainId ) = when { // parachain -> parachain - originChain.isParachain && destinationParaId != null -> SiblingParachain(destinationParaId) + originChain.isParachain && destinationParaId != null -> SiblingParachain(destinationParaId, destinationChainId) // parachain -> relaychain originChain.isParachain -> ParentChain() // relaychain -> parachain - destinationParaId != null -> ChildParachain(destinationParaId) + destinationParaId != null -> ChildParachain(destinationParaId, destinationChainId) // relaychain -> relaychain ? else -> throw UnsupportedOperationException("Unsupported cross-chain transfer") } -private fun ChildParachain(paraId: ParaId): RelativeMultiLocation { +private fun ChildParachain(paraId: ParaId, destinationChainId: ChainId): RelativeMultiLocation { + val junctionTypeName = junctionTypeNameForChain(destinationChainId) return RelativeMultiLocation( parents = 0, - interior = listOf(Junction.ParachainId(paraId)).toInterior() + interior = listOf(Junction.ParachainId(paraId, junctionTypeName)).toInterior() ) } @@ -162,10 +177,11 @@ private fun ParentChain(): RelativeMultiLocation { ) } -private fun SiblingParachain(paraId: ParaId): RelativeMultiLocation { +private fun SiblingParachain(paraId: ParaId, destinationChainId: ChainId): RelativeMultiLocation { + val junctionTypeName = junctionTypeNameForChain(destinationChainId) return RelativeMultiLocation( parents = 1, - listOf(Junction.ParachainId(paraId)).toInterior() + listOf(Junction.ParachainId(paraId, junctionTypeName)).toInterior() ) } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/mappers/crosschain/Dynamic.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/mappers/crosschain/Dynamic.kt index 43913be..950b5b9 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/mappers/crosschain/Dynamic.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/mappers/crosschain/Dynamic.kt @@ -64,8 +64,8 @@ private fun constructTransfersForChain(configRemote: DynamicCrossChainOriginChai destinations = assetConfig.xcmTransfers.map { transfer -> TransferDestination( fullChainAssetId = FullChainAssetId( - transfer.destination.chainId, - transfer.destination.assetId + transfer.getDestinationChainId(), + transfer.getDestinationAssetId() ), hasDeliveryFee = transfer.hasDeliveryFee ?: false, supportsXcmExecute = transfer.supportsXcmExecute ?: false, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainConfigRemote.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainConfigRemote.kt index c5042f6..d270050 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainConfigRemote.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainConfigRemote.kt @@ -33,11 +33,31 @@ class DynamicCrossChainOriginAssetRemote( ) class DynamicXcmTransferRemote( - val destination: XcmTransferDestinationRemote, + // New format: nested destination object + val destination: XcmTransferDestinationRemote?, + // Legacy format: chainId and assetId at root level + val chainId: ChainId?, + val assetId: Int?, val type: String?, val hasDeliveryFee: Boolean?, val supportsXcmExecute: Boolean?, -) +) { + /** + * Get the destination chainId, supporting both new and legacy formats. + */ + fun getDestinationChainId(): ChainId { + return destination?.chainId ?: chainId + ?: throw IllegalStateException("XCM transfer has no destination chainId") + } + + /** + * Get the destination assetId, supporting both new and legacy formats. + */ + fun getDestinationAssetId(): Int { + return destination?.assetId ?: assetId + ?: throw IllegalStateException("XCM transfer has no destination assetId") + } +} class XcmTransferDestinationRemote( val chainId: ChainId, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainWeigher.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainWeigher.kt index 0e545ec..78472ba 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainWeigher.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/DynamicCrossChainWeigher.kt @@ -1,11 +1,14 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.crosschain.dynamic +import android.util.Log import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.utils.LOG_TAG import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransferBase import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.replaceAmount import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.data.network.crosschain.CrossChainFeeModel +import io.novafoundation.nova.feature_wallet_api.data.network.crosschain.zero import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.domain.model.xcm.dynamic.DynamicCrossChainTransferConfiguration import io.novafoundation.nova.feature_wallet_api.data.network.crosschain.XcmTransferDryRunOrigin @@ -26,12 +29,32 @@ class DynamicCrossChainWeigher @Inject constructor( ): CrossChainFeeModel { val safeTransfer = transfer.ensureSafeAmount() val result = xcmTransferDryRunner.dryRunXcmTransfer(config, safeTransfer, XcmTransferDryRunOrigin.Fake) - .getOrThrow() - return CrossChainFeeModel.fromDryRunResult( - initialAmount = safeTransfer.amountPlanks, - transferDryRunResult = result - ) + return result.getOrNull()?.let { dryRunResult -> + CrossChainFeeModel.fromDryRunResult( + initialAmount = safeTransfer.amountPlanks, + transferDryRunResult = dryRunResult + ) + } ?: run { + // Dry run failed - use fallback fee estimation + // For teleport transfers, dry run often doesn't produce forwarded XCMs + Log.w(LOG_TAG, "Dry run failed for ${config.transferType}, using fallback fee estimation") + estimateFallbackFee(config, transfer) + } + } + + /** + * Fallback fee estimation when dry run fails. + * Uses a conservative percentage of the transfer amount as fee buffer. + */ + private fun estimateFallbackFee( + config: DynamicCrossChainTransferConfiguration, + transfer: AssetTransferBase + ): CrossChainFeeModel { + // Use 1% of transfer amount as conservative fee estimate for all transfer types + // This covers execution fees on destination chain + val estimatedFee = transfer.amountPlanks / 100.toBigInteger() + return CrossChainFeeModel(paidFromHolding = estimatedFee.coerceAtLeast(Balance.ZERO)) } // Ensure we can calculate fee regardless of what user entered diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/XcmTransferDryRunner.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/XcmTransferDryRunner.kt index dfd4ca1..3fdb4a0 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/XcmTransferDryRunner.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/XcmTransferDryRunner.kt @@ -4,7 +4,7 @@ import android.util.Log import io.novafoundation.nova.common.address.AccountIdKey import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.utils.LOG_TAG -import io.novafoundation.nova.common.utils.xcmPalletName +import io.novafoundation.nova.common.utils.xcmPalletNameOrNull import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransferBase import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.amount @@ -254,7 +254,7 @@ class RealXcmTransferDryRunner @Inject constructor( dryRunEffects: DryRunEffects, runtimeSnapshot: RuntimeSnapshot, ): Balance { - val xcmPalletName = runtimeSnapshot.metadata.xcmPalletName() + val xcmPalletName = runtimeSnapshot.metadata.xcmPalletNameOrNull() ?: return Balance.ZERO val event = dryRunEffects.emittedEvents.findEvent(xcmPalletName, "FeesPaid") ?: return Balance.ZERO val usedXcmVersion = dryRunEffects.senderXcmVersion() @@ -269,7 +269,7 @@ class RealXcmTransferDryRunner @Inject constructor( dryRunEffects: DryRunEffects, runtimeSnapshot: RuntimeSnapshot, ): Balance { - val xcmPalletName = runtimeSnapshot.metadata.xcmPalletName() + val xcmPalletName = runtimeSnapshot.metadata.xcmPalletNameOrNull() ?: return Balance.ZERO val event = dryRunEffects.emittedEvents.findEvent(xcmPalletName, "AssetsTrapped") ?: return Balance.ZERO val feesDecoded = event.arguments[ASSETS_TRAPPED_ARGUMENT_INDEX] @@ -290,10 +290,24 @@ class RealXcmTransferDryRunner @Inject constructor( dryRunEffects: DryRunEffects, destination: RelativeMultiLocation ): VersionedRawXcmMessage { + val forwardedXcms = dryRunEffects.forwardedXcms + + // For teleport transfers, forwarded XCMs might be empty or structured differently + if (forwardedXcms.isEmpty()) { + error("Dry run did not produce any forwarded XCMs. This transfer type may not support dry run fee estimation.") + } + val usedXcmVersion = dryRunEffects.senderXcmVersion() val versionedDestination = destination.versionedXcm(usedXcmVersion) - val forwardedXcmsToDestination = dryRunEffects.forwardedXcms.getByLocation(versionedDestination) + val forwardedXcmsToDestination = forwardedXcms.getByLocation(versionedDestination) + + // If destination location not found, try first available forwarded XCM + if (forwardedXcmsToDestination.isEmpty()) { + Log.w(LOG_TAG, "No forwarded XCM found for destination $destination, using first available") + val firstAvailable = forwardedXcms.firstOrNull()?.second?.firstOrNull() + return firstAvailable ?: error("No forwarded XCMs available for dry run") + } // There should only be one forwarded message during dry run return forwardedXcmsToDestination.first() diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/domain/RealCrossChainTransfersUseCase.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/domain/RealCrossChainTransfersUseCase.kt index d0519d9..3563445 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/domain/RealCrossChainTransfersUseCase.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/domain/RealCrossChainTransfersUseCase.kt @@ -198,6 +198,12 @@ internal class RealCrossChainTransfersUseCase( origin = origin ) .coerceToUnit() + .recoverCatching { error -> + // Dry run is optional - if it fails, log the error but don't block the transfer + // Some chains (like Pezkuwi) don't support dry run properly + Log.w(LOG_TAG, "Dry run failed but continuing with transfer: ${error.message}") + Unit + } } override suspend fun supportsXcmExecute(originChainId: ChainId, features: DynamicCrossChainTransferFeatures): Boolean { diff --git a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/runtimeApi/dryRun/model/DryRunEffects.kt b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/runtimeApi/dryRun/model/DryRunEffects.kt index e46289e..f4a266d 100644 --- a/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/runtimeApi/dryRun/model/DryRunEffects.kt +++ b/feature-xcm/api/src/main/java/io/novafoundation/nova/feature_xcm_api/runtimeApi/dryRun/model/DryRunEffects.kt @@ -12,6 +12,6 @@ interface DryRunEffects { fun DryRunEffects.senderXcmVersion(): XcmVersion { // For referencing destination, dry run uses sender's xcm version - val (destination) = forwardedXcms.first() - return destination.version + val firstForwarded = forwardedXcms.firstOrNull() + return firstForwarded?.first?.version ?: XcmVersion.GLOBAL_DEFAULT } diff --git a/feature-xcm/impl/src/main/java/io/novafoundation/nova/feature_xcm_impl/versions/detector/RealXcmVersionDetector.kt b/feature-xcm/impl/src/main/java/io/novafoundation/nova/feature_xcm_impl/versions/detector/RealXcmVersionDetector.kt index ce45269..b9c8a41 100644 --- a/feature-xcm/impl/src/main/java/io/novafoundation/nova/feature_xcm_impl/versions/detector/RealXcmVersionDetector.kt +++ b/feature-xcm/impl/src/main/java/io/novafoundation/nova/feature_xcm_impl/versions/detector/RealXcmVersionDetector.kt @@ -3,7 +3,7 @@ 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.common.utils.xcmPalletNameOrNull 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 @@ -26,7 +26,7 @@ class RealXcmVersionDetector @Inject constructor( override suspend fun lowestPresentMultiLocationVersion(chainId: ChainId): XcmVersion? { return lowestPresentXcmTypeVersionFromCallArgument( chainId = chainId, - getCall = { it.moduleOrNull(it.xcmPalletName())?.callOrNull("reserve_transfer_assets") }, + getCall = { it.xcmPalletNameOrNull()?.let { pallet -> it.moduleOrNull(pallet) }?.callOrNull("reserve_transfer_assets") }, argumentName = "dest" ) } @@ -34,7 +34,7 @@ class RealXcmVersionDetector @Inject constructor( override suspend fun lowestPresentMultiAssetsVersion(chainId: ChainId): XcmVersion? { return lowestPresentXcmTypeVersionFromCallArgument( chainId = chainId, - getCall = { it.moduleOrNull(it.xcmPalletName())?.callOrNull("reserve_transfer_assets") }, + getCall = { it.xcmPalletNameOrNull()?.let { pallet -> it.moduleOrNull(pallet) }?.callOrNull("reserve_transfer_assets") }, argumentName = "assets" ) } @@ -42,7 +42,7 @@ class RealXcmVersionDetector @Inject constructor( override suspend fun lowestPresentMultiAssetIdVersion(chainId: ChainId): XcmVersion? { return lowestPresentXcmTypeVersionFromCallArgument( chainId = chainId, - getCall = { it.moduleOrNull(it.xcmPalletName())?.callOrNull("transfer_assets_using_type_and_then") }, + getCall = { it.xcmPalletNameOrNull()?.let { pallet -> it.moduleOrNull(pallet) }?.callOrNull("transfer_assets_using_type_and_then") }, argumentName = "remote_fees_id" ) } @@ -55,7 +55,7 @@ class RealXcmVersionDetector @Inject constructor( val actualCheckedType = multiLocationType?.skipAliases() ?: return null val versionedType = getVersionedType( chainId = chainId, - getCall = { moduleOrNull(xcmPalletName())?.callOrNull("reserve_transfer_assets") }, + getCall = { xcmPalletNameOrNull()?.let { moduleOrNull(it) }?.callOrNull("reserve_transfer_assets") }, argumentName = "dest" ) ?: return null