mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-06-12 03:01:11 +00:00
fix: Add safety improvements for swap and XCM functionality
- Add independent chain warm-up with error handling - Add fallback fee estimation when dry run fails - Handle empty forwarded XCMs in dry run gracefully - Support both legacy and new XCM config destination formats - Use xcmPalletNameOrNull for safer pallet detection - Add Teyrchain junction support for legacy cross-chain config - Recover from dry run failures in cross-chain transfers - Add Pezkuwi Asset Hub to swap warm-up chains
This commit is contained in:
+42
-11
@@ -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<Unit> {
|
||||
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<QuotedEdge<SwapGraphEdge>>): 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<QuotedEdge<SwapGraphEdge>>): 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<FullChainAssetId, Token>.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<QuotedTrade>) {
|
||||
if (!debug) return
|
||||
|
||||
val allCandidates = quotedTrades.sortedDescending()
|
||||
.map { trade -> formatTrade(trade) }
|
||||
.joinToString(separator = "\n")
|
||||
|
||||
+24
-8
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+22
-2
@@ -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,
|
||||
|
||||
+28
-5
@@ -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
|
||||
|
||||
+18
-4
@@ -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()
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+2
-2
@@ -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
|
||||
}
|
||||
|
||||
+5
-5
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user