feat: Add Pezkuwi chain support with custom signed extensions

- Add PezkuwiAddressConstructor for custom address type handling
- Add custom signed extensions (CheckNonZeroSender, CheckWeight, WeightReclaim, PezkuwiCheckMortality)
- Add pezkuwi.json type definitions
- Update RuntimeSnapshotExt for multiple address type lookups
- Update CHAINS_URL to use GitHub-hosted chains.json with types config
- Add feeViaRuntimeCall support for Pezkuwi chains
- Add debug diagnostics for runtime type issues (to be cleaned before production)
- Add CHANGELOG_PEZKUWI.md documenting all changes
This commit is contained in:
2026-02-03 05:41:52 +03:00
parent bb189aeb8a
commit 032cbde2d6
29 changed files with 662 additions and 55 deletions
@@ -1,13 +1,15 @@
package io.novafoundation.nova.feature_wallet_api.data.network.blockhain
import io.novafoundation.nova.common.utils.Modules
import io.novafoundation.nova.common.utils.argumentType
import io.novafoundation.nova.common.utils.balances
import io.novafoundation.nova.common.utils.firstExistingCallName
import io.novafoundation.nova.common.utils.hasCall
import io.novafoundation.nova.runtime.util.constructAccountLookupInstance
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor
import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder
import io.novasama.substrate_sdk_android.runtime.extrinsic.call
import io.novasama.substrate_sdk_android.runtime.metadata.call
import java.math.BigInteger
enum class TransferMode {
@@ -25,11 +27,13 @@ fun ExtrinsicBuilder.nativeTransfer(accountId: AccountId, amount: BigInteger, mo
}
private fun ExtrinsicBuilder.transferKeepAlive(accountId: AccountId, amount: BigInteger) {
val destType = runtime.metadata.balances().call("transfer_keep_alive").argumentType("dest")
call(
moduleName = Modules.BALANCES,
callName = "transfer_keep_alive",
arguments = mapOf(
"dest" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, accountId),
"dest" to destType.constructAccountLookupInstance(accountId),
"value" to amount
)
)
@@ -37,12 +41,13 @@ private fun ExtrinsicBuilder.transferKeepAlive(accountId: AccountId, amount: Big
private fun ExtrinsicBuilder.transferAllowDeath(accountId: AccountId, amount: BigInteger) {
val callName = runtime.metadata.balances().firstExistingCallName("transfer_allow_death", "transfer")
val destType = runtime.metadata.balances().call(callName).argumentType("dest")
call(
moduleName = Modules.BALANCES,
callName = callName,
arguments = mapOf(
"dest" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, accountId),
"dest" to destType.constructAccountLookupInstance(accountId),
"value" to amount
)
)
@@ -52,11 +57,13 @@ private fun ExtrinsicBuilder.transferAll(accountId: AccountId, amount: BigIntege
val transferAllPresent = runtime.metadata.balances().hasCall("transfer_all")
if (transferAllPresent) {
val destType = runtime.metadata.balances().call("transfer_all").argumentType("dest")
call(
moduleName = Modules.BALANCES,
callName = "transfer_all",
arguments = mapOf(
"dest" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, accountId),
"dest" to destType.constructAccountLookupInstance(accountId),
"keep_alive" to false
)
)
@@ -144,13 +144,36 @@ internal class FeeLoaderV2Provider<F, D>(
private suspend fun onFeeError(error: Throwable, feeConstructor: FeeConstructor<F>) {
if (error !is CancellationException) {
Log.e(LOG_TAG, "Failed to sync fee", error)
// Build full error chain for debugging
val errorChain = buildString {
var current: Throwable? = error
var depth = 0
while (current != null && depth < 5) {
if (depth > 0) append(" -> ")
append("${current.javaClass.simpleName}: ${current.message}")
current = current.cause
depth++
}
}
val errorMsg = errorChain
Log.e(LOG_TAG, "Failed to sync fee: $errorMsg", error)
fee.emit(FeeStatus.Error)
awaitFeeRetry()
loadFee(feeConstructor)
// Show detailed error in retry dialog with runtime diagnostics
val diagnostics = try {
io.novafoundation.nova.runtime.multiNetwork.runtime.RuntimeFactory.lastDiagnostics
} catch (e: Exception) { "N/A" }
retryEvent.postValue(
Event(
RetryPayload(
title = resourceManager.getString(R.string.choose_amount_network_error),
message = "DEBUG: $errorMsg | Runtime: $diagnostics",
onRetry = { loadFee(feeConstructor) },
onCancel = { }
)
)
)
}
}