diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/ExtrinsicSplitter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/ExtrinsicSplitter.kt index b9644056..b3c93aec 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/ExtrinsicSplitter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/ExtrinsicSplitter.kt @@ -8,10 +8,8 @@ import io.novafoundation.nova.feature_account_api.data.signer.SigningContext import io.novafoundation.nova.common.utils.min import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSplitter import io.novafoundation.nova.feature_account_api.data.extrinsic.SplitCalls -import io.novafoundation.nova.runtime.ext.isPezkuwiChain import io.novafoundation.nova.runtime.ext.requireGenesisHash import io.novafoundation.nova.runtime.extrinsic.CustomTransactionExtensions -import io.novafoundation.nova.runtime.extrinsic.extensions.PezkuwiCheckImmortal import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -135,6 +133,12 @@ internal class RealExtrinsicSplitter @Inject constructor( return split } + /** + * A throwaway signed extrinsic, built only to measure the weight of `call`. + * + * Immortal era: this extrinsic is never submitted, so there is nothing for a mortal + * era to protect, and an immortal one needs no block hash lookup. + */ private suspend fun wrapInFakeExtrinsic( signer: NovaSigner, call: GenericCall.Instance, @@ -148,15 +152,7 @@ internal class RealExtrinsicSplitter @Inject constructor( extrinsicVersion = ExtrinsicVersion.V4, batchMode = BatchMode.BATCH, ).apply { - // Use custom CheckMortality for Pezkuwi chains to avoid DictEnum type lookup issues. - // Gated on chain identity (not signed-extension presence): both Pezkuwi and Polkadot - // Asset Hub declare "AuthorizeCall", so that alone can't tell the chains apart, and - // PezkuwiCheckImmortal's raw DictEnum value fails Polkadot's own Era type codec. - if (chain.isPezkuwiChain) { - setTransactionExtension(PezkuwiCheckImmortal(genesisHash)) - } else { - setTransactionExtension(CheckMortality(Era.Immortal, genesisHash)) - } + setTransactionExtension(CheckMortality(Era.Immortal, genesisHash)) setTransactionExtension(CheckGenesis(chain.requireGenesisHash().fromHex())) setTransactionExtension(ChargeTransactionPayment(BigInteger.ZERO)) setTransactionExtension(CheckMetadataHash(CheckMetadataHashMode.Disabled)) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckImmortal.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckImmortal.kt deleted file mode 100644 index 1cb3872d..00000000 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckImmortal.kt +++ /dev/null @@ -1,23 +0,0 @@ -package io.novafoundation.nova.runtime.extrinsic.extensions - -import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum -import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.FixedValueTransactionExtension - -/** - * Custom CheckMortality extension for Pezkuwi chains using IMMORTAL era. - * - * Pezkuwi uses pezsp_runtime.generic.era.Era which is a DictEnum with variants: - * - Immortal (encoded as 0x00) - * - Mortal1(u8), Mortal2(u8), ..., Mortal255(u8) - * - * This extension uses Immortal era with genesis hash, which matches how @pezkuwi/api signs. - * - * @param genesisHash The chain's genesis hash (32 bytes) for the signer payload - */ -class PezkuwiCheckImmortal( - genesisHash: ByteArray -) : FixedValueTransactionExtension( - name = "CheckMortality", - implicit = genesisHash, // Genesis hash goes into signer payload for immortal transactions - explicit = DictEnum.Entry("Immortal", null) // Immortal variant - unit type with no value -) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckMortality.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckMortality.kt deleted file mode 100644 index 520fbe78..00000000 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckMortality.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.novafoundation.nova.runtime.extrinsic.extensions - -import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum -import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.Era -import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.FixedValueTransactionExtension -import java.math.BigInteger - -/** - * Custom CheckMortality extension for Pezkuwi chains. - * - * Pezkuwi uses pezsp_runtime.generic.era.Era which is a DictEnum with variants: - * - Immortal - * - Mortal1(u8), Mortal2(u8), ..., Mortal255(u8) - * - * The variant name is "MortalX" where X is the first byte of the encoded era, - * and the variant's value is the second byte (u8). - * - * @param era The mortal era from MortalityConstructor - * @param blockHash The block hash (32 bytes) for the signer payload - */ -class PezkuwiCheckMortality( - era: Era.Mortal, - blockHash: ByteArray -) : FixedValueTransactionExtension( - name = "CheckMortality", - implicit = blockHash, // blockHash goes into signer payload - explicit = createEraEntry(era) // Era as DictEnum.Entry -) { - companion object { - /** - * Creates a DictEnum.Entry for the Era. - * - * Standard Era encoding produces 2 bytes: - * - First byte determines the variant name (Mortal1, Mortal2, ..., Mortal255) - * - Second byte is the variant's value (u8) - */ - private fun createEraEntry(era: Era.Mortal): DictEnum.Entry { - val period = era.period.toLong() - val phase = era.phase.toLong() - val quantizeFactor = maxOf(period shr 12, 1) - - // Calculate the two-byte encoding - val encoded = ((countTrailingZeroBits(period) - 1).coerceIn(1, 15)) or - ((phase / quantizeFactor).toInt() shl 4) - - val firstByte = encoded and 0xFF - val secondByte = (encoded shr 8) and 0xFF - - // DictEnum variant: "MortalX" where X is the first byte - // Variant value: second byte as u8 (BigInteger) - return DictEnum.Entry( - name = "Mortal$firstByte", - value = BigInteger.valueOf(secondByte.toLong()) - ) - } - - private fun countTrailingZeroBits(value: Long): Int { - if (value == 0L) return 64 - var n = 0 - var x = value - while ((x and 1L) == 0L) { - n++ - x = x shr 1 - } - return n - } - } -}