fix: gate PezkuwiCheckImmortal on chain identity, not extension presence

Confirmed via on-device logcat trace: approving a multisig operation on
Polkadot Asset Hub failed with "Failed to encode extension CheckMortality"
because isPezkuwi checked for the "AuthorizeCall" signed extension, which
Polkadot Asset Hub also declares - so PezkuwiCheckImmortal's raw DictEnum
value was used there too, and it isn't a valid instance of Polkadot's own
Era type codec. Switched the gate to the existing chain.isPezkuwiChain
(genesis-hash based) check already used for the bizinikiwi signing context
split. Also removes the temporary diagnostic logging added while tracing
this bug and the earlier deep-link routing fix.
This commit is contained in:
2026-07-18 19:04:12 -07:00
parent e0feb9581b
commit d999f48891
5 changed files with 21 additions and 32 deletions
@@ -444,7 +444,9 @@ class Navigator(
}
override fun openBridgeFlow() {
navigationBuilder().action(R.id.action_mainFragment_to_bridgeFlow)
navigationBuilder().cases()
.addCase(R.id.mainFragment, R.id.action_mainFragment_to_bridgeFlow)
.addCase(R.id.balanceDetailFragment, R.id.action_balanceDetailFragment_to_bridgeFlow)
.navigateInFirstAttachedContext()
}
@@ -225,11 +225,8 @@ class RootViewModel(
}
fun handleDeepLink(data: Uri) {
android.util.Log.e("DeepLinkDebug", "RootViewModel.handleDeepLink: raw=$data scheme=${data.scheme} authority=${data.authority} path=${data.path}")
launch {
val result = deepLinkHandler.handleDeepLink(data)
android.util.Log.e("DeepLinkDebug", "RootViewModel.handleDeepLink: result=$result exceptionOrNull=${result.exceptionOrNull()}")
result
deepLinkHandler.handleDeepLink(data)
.onFailureInstance<DeepLinkHandlingException, Unit> {
val errorMessage = formatDeepLinkHandlingException(resourceManager, it)
showError(errorMessage)
@@ -847,6 +847,14 @@
app:popUpTo="@id/balanceDetailFragment"
app:popUpToInclusive="true" />
<action
android:id="@+id/action_balanceDetailFragment_to_bridgeFlow"
app:destination="@id/bridgeFragment"
app:enterAnim="@anim/fragment_open_enter"
app:exitAnim="@anim/fragment_open_exit"
app:popEnterAnim="@anim/fragment_close_enter"
app:popExitAnim="@anim/fragment_close_exit" />
</fragment>
<fragment
@@ -8,6 +8,7 @@ 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
@@ -142,30 +143,19 @@ internal class RealExtrinsicSplitter @Inject constructor(
): SendableExtrinsic {
val genesisHash = chain.requireGenesisHash().fromHex()
val isPezkuwi = runtime.metadata.extrinsic.signedExtensions.any { it.id == "AuthorizeCall" }
android.util.Log.e(
"ExtrinsicDebug",
"wrapInFakeExtrinsic: chain=${chain.name} isPezkuwi=$isPezkuwi " +
"signedExtensionIds=${runtime.metadata.extrinsic.signedExtensions.map { it.id }}"
)
val builder = ExtrinsicBuilder(
runtime = runtime,
extrinsicVersion = ExtrinsicVersion.V4,
batchMode = BatchMode.BATCH,
).apply {
// Use custom CheckMortality for Pezkuwi chains to avoid DictEnum type lookup issues
if (isPezkuwi) {
android.util.Log.e("ExtrinsicDebug", "using PezkuwiCheckImmortal")
// 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 {
android.util.Log.e("ExtrinsicDebug", "using standard CheckMortality(Era.Immortal)")
runCatching {
setTransactionExtension(CheckMortality(Era.Immortal, genesisHash))
}.onFailure { e ->
android.util.Log.e("ExtrinsicDebug", "standard CheckMortality setTransactionExtension threw", e)
throw e
}
setTransactionExtension(CheckMortality(Era.Immortal, genesisHash))
}
setTransactionExtension(CheckGenesis(chain.requireGenesisHash().fromHex()))
setTransactionExtension(ChargeTransactionPayment(BigInteger.ZERO))
@@ -181,8 +171,6 @@ internal class RealExtrinsicSplitter @Inject constructor(
signer.setSignerDataForFee(signingContext)
}
return runCatching { builder.buildExtrinsic() }
.onFailure { e -> android.util.Log.e("ExtrinsicDebug", "buildExtrinsic threw", e) }
.getOrThrow()
return builder.buildExtrinsic()
}
}
@@ -41,15 +41,11 @@ class MultisigOperationDetailsDeepLinkHandler(
override suspend fun matches(data: Uri): Boolean {
val path = data.path ?: return false
val result = path.startsWith(MultisigOperationDeepLinkConfigurator.PREFIX)
android.util.Log.e("DeepLinkDebug", "matches: path=$path result=$result")
return result
return path.startsWith(MultisigOperationDeepLinkConfigurator.PREFIX)
}
override suspend fun handleDeepLink(data: Uri): Result<Unit> = runCatching {
android.util.Log.e("DeepLinkDebug", "handleDeepLink: start, awaiting interaction gate...")
automaticInteractionGate.awaitInteractionAllowed()
android.util.Log.e("DeepLinkDebug", "handleDeepLink: interaction gate passed")
val chainId = data.getChainId()
val chain = chainRegistry.getChain(chainId)
@@ -59,13 +55,11 @@ class MultisigOperationDetailsDeepLinkHandler(
val callHash = data.getCallHash() ?: error("Call hash not found")
val callData = data.getCallData(chainId)
val operationState = data.getOperationState()
android.util.Log.e("DeepLinkDebug", "parsed params ok, chainId=$chainId operationState=$operationState")
val multisigMetaAccount = accountRepository.getActiveMetaAccounts()
.filterIsInstance<MultisigMetaAccount>()
.firstOrNull { it.accountIdKeyIn(chain) == multisigAccount && it.signatoryAccountId == signatoryAccount }
?: error("Multisig account not found")
android.util.Log.e("DeepLinkDebug", "found multisigMetaAccount id=${multisigMetaAccount.id}")
accountRepository.selectMetaAccount(multisigMetaAccount.id)