diff --git a/CHANGELOG_PEZKUWI.md b/CHANGELOG_PEZKUWI.md new file mode 100644 index 0000000..4eff75a --- /dev/null +++ b/CHANGELOG_PEZKUWI.md @@ -0,0 +1,282 @@ +# PezWallet Android - Pezkuwi Uyumluluk Değişiklikleri + +Bu dosya, Pezkuwi chain uyumluluğu için yapılan tüm değişiklikleri takip eder. +Context sıfırlanması durumunda referans olarak kullanılmalıdır. + +--- + +## DEBUG KODLARI (Production öncesi KALDIRILMALI) + +### 1. FeeLoaderV2Provider.kt - Hata mesajı gösterimi +**Dosya:** `feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/v2/FeeLoaderV2Provider.kt` +**Değişiklik:** +```kotlin +// ÖNCE: +message = resourceManager.getString(R.string.choose_amount_error_fee), + +// SONRA (DEBUG): +message = "DEBUG: $errorMsg | Runtime: $diagnostics", +``` +**Temizleme:** +- `"DEBUG: $errorMsg | Runtime: $diagnostics"` → `resourceManager.getString(R.string.choose_amount_error_fee)` olarak geri al +- `val diagnostics = try { ... }` bloğunu kaldır + +--- + +### 2. RuntimeFactory.kt - Diagnostic değişken ve log'lar +**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt` +**Eklenenler:** +```kotlin +// Companion object içinde: +companion object { + @Volatile + var lastDiagnostics: String = "not yet initialized" +} + +// constructRuntimeInternal içinde: +lastDiagnostics = "typesUsage=$typesUsage, ExtrinsicSig=$hasExtrinsicSignature, MultiSig=$hasMultiSignature, typeCount=${types.size}" + +// Log satırları: +Log.d("RuntimeFactory", "DEBUG: TypesUsage for chain $chainId = $typesUsage") +Log.d("RuntimeFactory", "DEBUG: Loading BASE types for $chainId") +Log.d("RuntimeFactory", "DEBUG: BASE types loaded, hash=$baseHash, typeCount=${types.size}") +Log.d("RuntimeFactory", "DEBUG: Chain $chainId - ExtrinsicSignature=$hasExtrinsicSignature, MultiSignature=$hasMultiSignature, typesUsage=$typesUsage, typeCount=${types.size}") +Log.d("RuntimeFactory", "DEBUG: BaseTypes loaded, length=${baseTypesRaw.length}, contains ExtrinsicSignature=${baseTypesRaw.contains("ExtrinsicSignature")}") +Log.e("RuntimeFactory", "DEBUG: BaseTypes NOT in cache!") +``` +**Temizleme:** +- `companion object { ... }` bloğunu kaldır +- `lastDiagnostics = ...` satırını kaldır +- Tüm `Log.d/Log.e("RuntimeFactory", "DEBUG: ...")` satırlarını kaldır + +--- + +### 3. CustomTransactionExtensions.kt - Log satırları +**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/CustomTransactionExtensions.kt` +**Temizlenecek:** Tüm `Log.d(TAG, ...)` satırları ve `private const val TAG` tanımı + +--- + +### 4. ExtrinsicBuilderFactory.kt - Log satırları +**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt` +**Temizlenecek:** Tüm `Log.d(TAG, ...)` satırları ve `private const val TAG` tanımı + +--- + +### 5. PezkuwiAddressConstructor.kt - Log satırları +**Dosya:** `common/src/main/java/io/novafoundation/nova/common/utils/PezkuwiAddressConstructor.kt` +**Temizlenecek:** Tüm `Log.d(TAG, ...)` satırları ve `private const val TAG` tanımı + +--- + +## FEATURE DEĞİŞİKLİKLERİ (Kalıcı) + +### 1. PezkuwiAddressConstructor.kt - YENİ DOSYA +**Dosya:** `common/src/main/java/io/novafoundation/nova/common/utils/PezkuwiAddressConstructor.kt` +**Açıklama:** Pezkuwi chain'leri için özel address constructor. SDK'nın AddressInstanceConstructor'ı "Address" type'ını ararken, Pezkuwi "pezsp_runtime::multiaddress::MultiAddress" kullanıyor. + +--- + +### 2. RuntimeSnapshotExt.kt - Address type lookup +**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/util/RuntimeSnapshotExt.kt` +**Değişiklik:** Birden fazla address type ismi deneniyor: +```kotlin +val addressType = typeRegistry["Address"] + ?: typeRegistry["MultiAddress"] + ?: typeRegistry["sp_runtime::multiaddress::MultiAddress"] + ?: typeRegistry["pezsp_runtime::multiaddress::MultiAddress"] + ?: return false +``` + +--- + +### 3. Signed Extension Dosyaları - YENİ DOSYALAR +**Dosyalar:** +- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/AuthorizeCall.kt` +- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckNonZeroSender.kt` +- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckWeight.kt` +- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/WeightReclaim.kt` +- `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckMortality.kt` + +**Açıklama:** Pezkuwi chain'leri için özel signed extension'lar + +--- + +### 3.1. PezkuwiCheckMortality.kt - YENİ DOSYA +**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckMortality.kt` +**Açıklama:** SDK'nın CheckMortality'si metadata type lookup yaparak encode ediyor ve Pezkuwi'de bu başarısız oluyordu ("failed to encode extension CheckMortality" hatası). Bu custom extension, Era ve blockHash'i doğrudan encode ediyor. +**Neden gerekli:** SDK CheckMortality, Era type'ını metadata'dan arıyor. Pezkuwi metadata'sında Era type'ı `pezsp_runtime.generic.era.Era` DictEnum olarak tanımlı ve SDK bunu handle edemiyor. + +**Kod:** +```kotlin +class PezkuwiCheckMortality( + era: Era.Mortal, + blockHash: ByteArray +) : FixedValueTransactionExtension( + name = "CheckMortality", + implicit = blockHash, // blockHash goes into signer payload + explicit = createEraEntry(era) // Era as DictEnum.Entry +) +``` + +--- + +### 4. CustomTransactionExtensions.kt - Pezkuwi extension logic +**Dosya:** `runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/CustomTransactionExtensions.kt` +**Değişiklik:** `isPezkuwiChain()` fonksiyonu eklendi, Pezkuwi için farklı extension'lar kullanılıyor + +--- + +### 5. Address encoding yaklaşımı değişikliği +**Eski yaklaşım:** `AddressInstanceConstructor` veya `PezkuwiAddressConstructor` ile type ismine göre tahmin +**Yeni yaklaşım:** `argumentType("dest").constructAccountLookupInstance(accountId)` ile metadata'dan gerçek type alınıyor + +**Güncellenen dosyalar:** +1. `feature-wallet-api/.../ExtrinsicBuilderExt.kt` - **YENİ YAKLAŞIM**: metadata'dan type alıyor +2. `feature-governance-impl/.../ExtrinsicBuilderExt.kt` - Zaten doğru yaklaşımı kullanıyordu +3. Diğer dosyalar hala PezkuwiAddressConstructor kullanıyor (gerekirse güncellenecek): + - `feature-staking-impl/.../ExtrinsicBuilderExt.kt` + - `feature-staking-impl/.../NominationPoolsCalls.kt` + - `feature-proxy-api/.../ExtrinsicBuilderExt.kt` + - `feature-wallet-impl/.../StatemineAssetTransfers.kt` + - `feature-wallet-impl/.../OrmlAssetTransfers.kt` + - `feature-wallet-impl/.../NativeAssetIssuer.kt` + - `feature-wallet-impl/.../OrmlAssetIssuer.kt` + - `feature-wallet-impl/.../StatemineAssetIssuer.kt` + - `feature-account-impl/.../ProxiedSigner.kt` + +--- + +### 6. chains.json - feeViaRuntimeCall ve types +**Dosya:** `pezkuwi-config/chains.json` +**Değişiklik:** +- Pezkuwi chain'lerine `"feeViaRuntimeCall": true` eklendi +- `"types": { "overridesCommon": false }` eklendi (base types yüklenmesi için - ExtrinsicSignature type hatası düzeltmek için) +**Etkilenen chain'ler:** +- Pezkuwi Mainnet (bb4a61ab0c4b8c12f5eab71d0c86c482e03a275ecdafee678dea712474d33d75) +- Pezkuwi Asset Hub (00d0e1d0581c3cd5c5768652d52f4520184018b44f56a2ae1e0dc9d65c00c948) +- Pezkuwi People Chain (58269e9c184f721e0309332d90cafc410df1519a5dc27a5fd9b3bf5fd2d129f8) +- Zagros Testnet (96eb58af1bb7288115b5e4ff1590422533e749293f231974536dc6672417d06f) + +--- + +### 7. pezkuwi.json - Chain-specific types (ASSETS) +**Dosya:** `runtime/src/main/assets/types/pezkuwi.json` +**Açıklama:** Pezkuwi chain'leri için özel type tanımları +```json +{ + "types": { + "ExtrinsicSignature": "MultiSignature", + "Address": "pezsp_runtime::multiaddress::MultiAddress", + "LookupSource": "pezsp_runtime::multiaddress::MultiAddress" + }, + "typesAlias": { + "pezsp_runtime::multiaddress::MultiAddress": "MultiAddress", + "pezsp_runtime::MultiSignature": "MultiSignature", + "pezsp_runtime.generic.era.Era": "Era" + } +} +``` +**NOT:** Bu dosya şu anda kullanılmıyor çünkü TypesUsage.BASE kullanılıyor. TypesUsage.BOTH veya OWN için chains.json'da URL eklenebilir. + +--- + +## SORUN GEÇMİŞİ + +1. **"Network not responding"** - Fee hesaplama hatası + - Çözüm: feeViaRuntimeCall eklendi, custom signed extension'lar eklendi + +2. **"IllegalStateException: Type Address was not found"** - Address type lookup hatası + - Çözüm: RuntimeSnapshotExt.kt'de birden fazla type ismi deneniyor + +3. **"EncodeDecodeException: is not a valid instance"** - Address encoding hatası + - Çözüm: `argumentType("dest").constructAccountLookupInstance(accountId)` ile metadata'dan gerçek type alınıyor (ExtrinsicBuilderExt.kt) + +4. **"failed to encode extension CheckMortality"** - CheckMortality encoding hatası + - SDK'nın CheckMortality'si metadata type lookup yaparak Era'yı encode etmeye çalışıyor + - Pezkuwi Era type'ı `pezsp_runtime.generic.era.Era` DictEnum olarak tanımlı + - Çözüm: `PezkuwiCheckMortality` custom extension'ı oluşturuldu, Era'yı `DictEnum.Entry("MortalX", secondByte)` olarak veriyor + +5. **"IllegalStateException: Type ExtrinsicSignature was not found"** - ExtrinsicSignature type hatası (DEVAM EDİYOR) + - SDK "ExtrinsicSignature" type'ını arıyor ama Pezkuwi chain'leri `"types": null` kullanıyordu + - `TypesUsage.NONE` olduğu için base types (default.json) yüklenmiyordu + - Denenen çözüm: chains.json'da `"types": { "overridesCommon": false }` eklendi → `TypesUsage.BASE` kullanılıyor + - Base types URL: `https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/chains/types/default.json` + - default.json'da `"ExtrinsicSignature": "MultiSignature"` tanımlı + - Cache temizlenip uninstall/reinstall yapıldı ama hata devam ediyor + - Debug diagnostics eklendi - test sonucunu bekliyoruz + +--- + +## ÇALIŞAN İMPLEMENTASYONLAR (Referans) + +### 1. pezkuwi-extension (Browser Extension) +**Konum:** `/home/mamostehp/pezkuwi-extension/` +**Nasıl çalışıyor:** +- `@pezkuwi/types` (polkadot.js fork) kullanıyor +- `TypeRegistry` ile dynamic type handling +- Custom user extensions: +```javascript +const PEZKUWI_USER_EXTENSIONS = { + AuthorizeCall: { + extrinsic: {}, + payload: {} + } +}; +``` +- `registry.setSignedExtensions(payload.signedExtensions, PEZKUWI_USER_EXTENSIONS)` ile extension'lar ekleniyor +- Metadata'dan registry oluşturuluyor: `metadataExpand(metadata, false)` + +### 2. pezkuwi-subxt (Rust) +**Konum:** `/home/mamostehp/pezkuwi-sdk/vendor/pezkuwi-subxt/` +**Nasıl çalışıyor:** +- Rust'ta compile-time type generation +- Metadata'dan otomatik type oluşturma + +### 3. Telegram Miniapp +- Web tabanlı, polkadot.js kullanıyor +- `"types": null` ile çalışıyor çünkü metadata v14+ self-contained + +--- + +## TEMİZLEME KONTROL LİSTESİ + +Production release öncesi yapılacaklar: + +- [ ] FeeLoaderV2Provider.kt - DEBUG mesajını ve diagnostics'i kaldır +- [ ] RuntimeFactory.kt - companion object ve debug log'ları kaldır +- [ ] CustomTransactionExtensions.kt - Log satırlarını kaldır +- [ ] ExtrinsicBuilderFactory.kt - Log satırlarını kaldır +- [ ] PezkuwiAddressConstructor.kt - Log satırlarını kaldır (varsa) +- [ ] Test et: Transfer işlemi çalışıyor mu? +- [ ] Test et: Fee hesaplama çalışıyor mu? + +--- + +## TYPE LOADING AKIŞI (Referans) + +``` +chains.json + ↓ +"types": { "overridesCommon": false } → TypesUsage.BASE +"types": { "url": "...", "overridesCommon": false } → TypesUsage.BOTH +"types": { "url": "...", "overridesCommon": true } → TypesUsage.OWN +"types": null → TypesUsage.NONE + ↓ +RuntimeFactory.constructRuntime() + ↓ +TypesUsage.BASE → constructBaseTypes() → fetch from DEFAULT_TYPES_URL +TypesUsage.BOTH → constructBaseTypes() + constructOwnTypes() +TypesUsage.OWN → constructOwnTypes() only +TypesUsage.NONE → use v14Preset() only + ↓ +TypeRegistry + ↓ +RuntimeSnapshot +``` + +**DEFAULT_TYPES_URL:** `https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/chains/types/default.json` + +--- + +*Son güncelleme: 2026-02-03 (Debug diagnostics eklendi, extension analizi yapıldı)* diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/PezkuwiAddressConstructor.kt b/common/src/main/java/io/novafoundation/nova/common/utils/PezkuwiAddressConstructor.kt new file mode 100644 index 0000000..c28ee35 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/PezkuwiAddressConstructor.kt @@ -0,0 +1,72 @@ +package io.novafoundation.nova.common.utils + +import android.util.Log +import io.novasama.substrate_sdk_android.runtime.AccountId +import io.novasama.substrate_sdk_android.runtime.definitions.registry.TypeRegistry +import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum +import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.MULTI_ADDRESS_ID +import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.FixedByteArray +import io.novasama.substrate_sdk_android.runtime.definitions.types.skipAliases + +private const val TAG = "PezkuwiAddressConstructor" + +/** + * Custom address constructor that handles Pezkuwi chains which use different type names. + * Pezkuwi uses "pezsp_runtime::multiaddress::MultiAddress" instead of standard "Address". + */ +object PezkuwiAddressConstructor { + + private val ADDRESS_TYPE_NAMES = listOf( + "Address", + "MultiAddress", + "sp_runtime::multiaddress::MultiAddress", + "pezsp_runtime::multiaddress::MultiAddress" + ) + + /** + * Constructs an address instance compatible with both standard Substrate and Pezkuwi chains. + * Checks the actual type structure to determine the correct encoding format. + */ + fun constructInstance(typeRegistry: TypeRegistry, accountId: AccountId): Any { + // Try to find the address type + var foundTypeName: String? = null + val addressType = ADDRESS_TYPE_NAMES.firstNotNullOfOrNull { name -> + typeRegistry[name]?.also { foundTypeName = name } + } + + Log.d(TAG, "Found address type: $foundTypeName, type class: ${addressType?.javaClass?.simpleName}") + + // If no address type found, return the raw accountId (for chains with simple AccountId) + if (addressType == null) { + Log.d(TAG, "No address type found, returning raw accountId") + return accountId + } + + val resolvedType = addressType.skipAliases() + Log.d(TAG, "Resolved type after skipAliases: ${resolvedType?.javaClass?.simpleName}, name: ${resolvedType?.name}") + + // Check the actual type structure + return when (resolvedType) { + is DictEnum -> { + Log.d(TAG, "Type is DictEnum with variants: ${resolvedType.elements.keys}") + // MultiAddress type - wrap in Id variant + DictEnum.Entry(MULTI_ADDRESS_ID, accountId) + } + is FixedByteArray -> { + Log.d(TAG, "Type is FixedByteArray with length: ${resolvedType.length}, returning raw accountId") + // GenericAccountId or similar - return raw + accountId + } + null -> { + Log.d(TAG, "Resolved type is null, returning raw accountId for Pezkuwi") + // For Pezkuwi, if alias doesn't resolve, try raw accountId + accountId + } + else -> { + Log.d(TAG, "Unknown type: ${resolvedType.javaClass.simpleName}, returning raw accountId") + // Unknown type, try raw accountId instead of DictEnum + accountId + } + } + } +} diff --git a/docs/ic_pezkuwi_launcher.png b/docs/ic_pezkuwi_launcher.png new file mode 100644 index 0000000..cada654 Binary files /dev/null and b/docs/ic_pezkuwi_launcher.png differ diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/fee/FeePaymentProviderRegistry.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/fee/FeePaymentProviderRegistry.kt index 19987b8..d2fda65 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/fee/FeePaymentProviderRegistry.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/fee/FeePaymentProviderRegistry.kt @@ -20,7 +20,9 @@ internal class RealFeePaymentProviderRegistry( val chain = chainRegistry.getChain(chainId) return when (chainId) { - Chain.Geneses.POLKADOT_ASSET_HUB -> assetHubFactory.create(chain) + Chain.Geneses.PEZKUWI_ASSET_HUB, + Chain.Geneses.POLKADOT_ASSET_HUB, + Chain.Geneses.KUSAMA_ASSET_HUB -> assetHubFactory.create(chain) Chain.Geneses.HYDRA_DX -> hydrationFactory.create(chain) else -> DefaultFeePaymentProvider(chain) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt index f62160f..7f5e791 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt @@ -30,7 +30,7 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novasama.substrate_sdk_android.runtime.AccountId import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignedRaw import io.novasama.substrate_sdk_android.runtime.extrinsic.signer.SignerPayloadRaw @@ -205,7 +205,7 @@ class ProxiedSigner( moduleName = Modules.PROXY, callName = "proxy", arguments = mapOf( - "real" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, proxiedAccountId), + "real" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, proxiedAccountId), "force_proxy_type" to DictEnum.Entry(proxyType.name, null), "call" to proxiedCall ) diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/PolkadotExternalSignInteractor.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/PolkadotExternalSignInteractor.kt index f0b512a..0dea7a6 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/PolkadotExternalSignInteractor.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/polkadot/PolkadotExternalSignInteractor.kt @@ -231,7 +231,7 @@ class PolkadotExternalSignInteractor( setTransactionExtension(CheckTxVersion(transactionVersion)) call(parsedExtrinsic.call) - CustomTransactionExtensions.applyDefaultValues(builder = this) + CustomTransactionExtensions.applyDefaultValues(builder = this, runtime = runtime) applyCustomSignedExtensions(parsedExtrinsic) signer.setSignerData(signingContext, signingMode) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/network/blockchain/extrinsic/ExtrinsicBuilderExt.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/network/blockchain/extrinsic/ExtrinsicBuilderExt.kt index cc2f18a..32d1ed9 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/network/blockchain/extrinsic/ExtrinsicBuilderExt.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/data/network/blockchain/extrinsic/ExtrinsicBuilderExt.kt @@ -13,7 +13,7 @@ import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Con import io.novafoundation.nova.runtime.util.constructAccountLookupInstance import io.novasama.substrate_sdk_android.runtime.AccountId import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor 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 @@ -55,7 +55,7 @@ fun ExtrinsicBuilder.convictionVotingUnlock( callName = "unlock", arguments = mapOf( "class" to trackId.value, - "target" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, accountId) + "target" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, accountId) ) ) } @@ -137,7 +137,7 @@ fun CallBuilder.convictionVotingDelegate( callName = "delegate", arguments = mapOf( "class" to trackId.value, - "to" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, delegate), + "to" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, delegate), "conviction" to conviction.prepareForEncoding(), "balance" to amount ) diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/calls/ExtrinsicBuilderExt.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/calls/ExtrinsicBuilderExt.kt index 8aaf0c1..45ed235 100644 --- a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/calls/ExtrinsicBuilderExt.kt +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/calls/ExtrinsicBuilderExt.kt @@ -6,7 +6,7 @@ import java.math.BigInteger import io.novasama.substrate_sdk_android.runtime.AccountId import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder import io.novasama.substrate_sdk_android.runtime.extrinsic.call @@ -28,7 +28,7 @@ fun ExtrinsicBuilder.removeProxyCall(proxyAccountId: AccountId, proxyType: Proxy private fun argumentsForProxy(runtime: RuntimeSnapshot, proxyAccountId: AccountId, proxyType: ProxyType): Map { return mapOf( - "delegate" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, proxyAccountId), + "delegate" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, proxyAccountId), "proxy_type" to DictEnum.Entry(proxyType.name, null), "delay" to BigInteger.ZERO ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/calls/ExtrinsicBuilderExt.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/calls/ExtrinsicBuilderExt.kt index c3b7cce..9d0f689 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/calls/ExtrinsicBuilderExt.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/calls/ExtrinsicBuilderExt.kt @@ -6,7 +6,7 @@ import io.novafoundation.nova.common.utils.voterListName import io.novafoundation.nova.feature_staking_api.domain.model.RewardDestination import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindRewardDestination import io.novasama.substrate_sdk_android.runtime.AccountId -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder import io.novasama.substrate_sdk_android.runtime.extrinsic.call import java.math.BigInteger @@ -110,7 +110,7 @@ fun ExtrinsicBuilder.rebag(dislocated: AccountId): ExtrinsicBuilder { moduleName = runtime.metadata.voterListName(), callName = "rebag", arguments = mapOf( - "dislocated" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, dislocated) + "dislocated" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, dislocated) ) ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/nominationPools/network/blockhain/calls/NominationPoolsCalls.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/nominationPools/network/blockhain/calls/NominationPoolsCalls.kt index b0c39a9..e6e4bc1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/nominationPools/network/blockhain/calls/NominationPoolsCalls.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/nominationPools/network/blockhain/calls/NominationPoolsCalls.kt @@ -6,7 +6,7 @@ import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network. import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novasama.substrate_sdk_android.runtime.AccountId import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder import io.novasama.substrate_sdk_android.runtime.extrinsic.call import java.math.BigInteger @@ -47,7 +47,7 @@ fun NominationPoolsCalls.unbond(unbondAccount: AccountId, unbondPoints: PoolPoin moduleName = Modules.NOMINATION_POOLS, callName = "unbond", arguments = mapOf( - "member_account" to AddressInstanceConstructor.constructInstance(extrinsicBuilder.runtime.typeRegistry, unbondAccount), + "member_account" to PezkuwiAddressConstructor.constructInstance(extrinsicBuilder.runtime.typeRegistry, unbondAccount), "unbonding_points" to unbondPoints.value ) ) @@ -58,7 +58,7 @@ fun NominationPoolsCalls.withdrawUnbonded(memberAccount: AccountId, numberOfSlas moduleName = Modules.NOMINATION_POOLS, callName = "withdraw_unbonded", arguments = mapOf( - "member_account" to AddressInstanceConstructor.constructInstance(extrinsicBuilder.runtime.typeRegistry, memberAccount), + "member_account" to PezkuwiAddressConstructor.constructInstance(extrinsicBuilder.runtime.typeRegistry, memberAccount), "num_slashing_spans" to numberOfSlashingSpans ) ) @@ -77,7 +77,7 @@ fun NominationPoolsCalls.migrateDelegation(memberAccount: AccountId) { moduleName = Modules.NOMINATION_POOLS, callName = "migrate_delegation", arguments = mapOf( - "member_account" to AddressInstanceConstructor.constructInstance(extrinsicBuilder.runtime.typeRegistry, memberAccount), + "member_account" to PezkuwiAddressConstructor.constructInstance(extrinsicBuilder.runtime.typeRegistry, memberAccount), ) ) } diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/ExtrinsicBuilderExt.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/ExtrinsicBuilderExt.kt index 5212a22..d6ca30b 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/ExtrinsicBuilderExt.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/ExtrinsicBuilderExt.kt @@ -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 ) ) diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/v2/FeeLoaderV2Provider.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/v2/FeeLoaderV2Provider.kt index a170428..bb7ee62 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/v2/FeeLoaderV2Provider.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/v2/FeeLoaderV2Provider.kt @@ -144,13 +144,36 @@ internal class FeeLoaderV2Provider( private suspend fun onFeeError(error: Throwable, feeConstructor: FeeConstructor) { 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 = { } + ) + ) + ) } } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/orml/OrmlAssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/orml/OrmlAssetTransfers.kt index dd80a18..83dea09 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/orml/OrmlAssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/orml/OrmlAssetTransfers.kt @@ -24,7 +24,7 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.getRuntime import io.novasama.substrate_sdk_android.runtime.AccountId import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder import io.novasama.substrate_sdk_android.runtime.extrinsic.call import java.math.BigInteger @@ -88,7 +88,7 @@ open class OrmlAssetTransfers( moduleIndex = moduleIndex, callIndex = callIndex, arguments = mapOf( - "dest" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, target), + "dest" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, target), "currency_id" to chainAsset.ormlCurrencyId(runtime), "amount" to amount ) diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/statemine/StatemineAssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/statemine/StatemineAssetTransfers.kt index 269925c..62ac3ff 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/statemine/StatemineAssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/statemine/StatemineAssetTransfers.kt @@ -27,7 +27,7 @@ import io.novafoundation.nova.runtime.multiNetwork.getRuntime import io.novafoundation.nova.runtime.storage.source.StorageDataSource import io.novasama.substrate_sdk_android.runtime.AccountId import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor 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.storage @@ -109,7 +109,7 @@ class StatemineAssetTransfers( callName = "transfer", arguments = mapOf( "id" to assetType.prepareIdForEncoding(runtime), - "target" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, target), + "target" to PezkuwiAddressConstructor.constructInstance(runtime.typeRegistry, target), "amount" to amount ) ) diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/NativeAssetIssuer.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/NativeAssetIssuer.kt index 6caab3c..d254e79 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/NativeAssetIssuer.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/NativeAssetIssuer.kt @@ -6,7 +6,7 @@ import io.novafoundation.nova.common.utils.composeCall import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor class NativeAssetIssuer( private val runtimeSnapshot: RuntimeSnapshot @@ -17,7 +17,7 @@ class NativeAssetIssuer( moduleName = Modules.BALANCES, callName = "force_set_balance", arguments = mapOf( - "who" to AddressInstanceConstructor.constructInstance(runtimeSnapshot.typeRegistry, destination.value), + "who" to PezkuwiAddressConstructor.constructInstance(runtimeSnapshot.typeRegistry, destination.value), "new_free" to amount ) ) diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/OrmlAssetIssuer.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/OrmlAssetIssuer.kt index 7e90af9..c374a53 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/OrmlAssetIssuer.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/OrmlAssetIssuer.kt @@ -8,7 +8,7 @@ import io.novafoundation.nova.runtime.ext.currencyId import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor class OrmlAssetIssuer( private val ormlType: Chain.Asset.Type.Orml, @@ -20,7 +20,7 @@ class OrmlAssetIssuer( moduleName = Modules.TOKENS, callName = "set_balance", arguments = mapOf( - "who" to AddressInstanceConstructor.constructInstance(runtimeSnapshot.typeRegistry, destination.value), + "who" to PezkuwiAddressConstructor.constructInstance(runtimeSnapshot.typeRegistry, destination.value), "currency_id" to ormlType.currencyId(runtimeSnapshot), "new_free" to amount, "new_reserved" to Balance.ZERO diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/StatemineAssetIssuer.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/StatemineAssetIssuer.kt index 3cc255f..f4c26c1 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/StatemineAssetIssuer.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/dynamic/dryRun/issuing/StatemineAssetIssuer.kt @@ -12,7 +12,7 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.chain.model.prepareIdForEncoding import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot import io.novasama.substrate_sdk_android.runtime.definitions.types.generics.GenericCall -import io.novasama.substrate_sdk_android.runtime.definitions.types.instances.AddressInstanceConstructor +import io.novafoundation.nova.common.utils.PezkuwiAddressConstructor class StatemineAssetIssuer( private val chainId: ChainId, @@ -38,7 +38,7 @@ class StatemineAssetIssuer( callName = "mint", arguments = mapOf( "id" to assetType.prepareIdForEncoding(runtimeSnapshot), - "beneficiary" to AddressInstanceConstructor.constructInstance(runtimeSnapshot.typeRegistry, destination.value), + "beneficiary" to PezkuwiAddressConstructor.constructInstance(runtimeSnapshot.typeRegistry, destination.value), "amount" to amount ) ) diff --git a/pezkuwi-config/chains.json b/pezkuwi-config/chains.json index f54fd91..de7449c 100644 --- a/pezkuwi-config/chains.json +++ b/pezkuwi-config/chains.json @@ -53,10 +53,13 @@ "typeExtras": null } ], - "types": null, + "types": { + "overridesCommon": false + }, "additional": { "themeColor": "#009639", - "defaultBlockTimeMillis": 6000 + "defaultBlockTimeMillis": 6000, + "feeViaRuntimeCall": true } }, { @@ -88,8 +91,12 @@ "icon": "https://pezkuwichain.io/tokens/HEZ.png" } ], + "types": { + "overridesCommon": false + }, "additional": { - "themeColor": "#009639" + "themeColor": "#009639", + "feeViaRuntimeCall": true } }, { @@ -166,10 +173,13 @@ } } ], - "types": null, + "types": { + "overridesCommon": false + }, "additional": { "themeColor": "#009639", - "defaultBlockTimeMillis": 6000 + "defaultBlockTimeMillis": 6000, + "feeViaRuntimeCall": true } }, { @@ -214,10 +224,13 @@ "typeExtras": null } ], - "types": null, + "types": { + "overridesCommon": false + }, "additional": { "themeColor": "#009639", - "defaultBlockTimeMillis": 6000 + "defaultBlockTimeMillis": 6000, + "feeViaRuntimeCall": true } }, { diff --git a/runtime/build.gradle b/runtime/build.gradle index 8600446..673767d 100644 --- a/runtime/build.gradle +++ b/runtime/build.gradle @@ -7,7 +7,7 @@ android { - buildConfigField "String", "CHAINS_URL", "\"https://wallet.pezkuwichain.io/chains.json\"" + buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/chains/v22/android/chains.json\"" buildConfigField "String", "EVM_ASSETS_URL", "\"https://wallet.pezkuwichain.io/evm_assets.json\"" buildConfigField "String", "PRE_CONFIGURED_CHAINS_URL", "\"https://wallet.pezkuwichain.io/pre_configured_chains.json\"" buildConfigField "String", "PRE_CONFIGURED_CHAIN_DETAILS_URL", "\"https://wallet.pezkuwichain.io/chain_details\"" @@ -27,7 +27,7 @@ android { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - buildConfigField "String", "CHAINS_URL", "\"https://wallet.pezkuwichain.io/chains.json\"" + buildConfigField "String", "CHAINS_URL", "\"https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/chains/v22/android/chains.json\"" buildConfigField "String", "EVM_ASSETS_URL", "\"https://wallet.pezkuwichain.io/evm_assets.json\"" buildConfigField "String", "PRE_CONFIGURED_CHAINS_URL", "\"https://wallet.pezkuwichain.io/pre_configured_chains.json\"" buildConfigField "String", "PRE_CONFIGURED_CHAIN_DETAILS_URL", "\"https://wallet.pezkuwichain.io/chain_details\"" diff --git a/runtime/src/main/assets/types/pezkuwi.json b/runtime/src/main/assets/types/pezkuwi.json new file mode 100644 index 0000000..7e71938 --- /dev/null +++ b/runtime/src/main/assets/types/pezkuwi.json @@ -0,0 +1,12 @@ +{ + "types": { + "ExtrinsicSignature": "MultiSignature", + "Address": "pezsp_runtime::multiaddress::MultiAddress", + "LookupSource": "pezsp_runtime::multiaddress::MultiAddress" + }, + "typesAlias": { + "pezsp_runtime::multiaddress::MultiAddress": "MultiAddress", + "pezsp_runtime::MultiSignature": "MultiSignature", + "pezsp_runtime.generic.era.Era": "Era" + } +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/CustomTransactionExtensions.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/CustomTransactionExtensions.kt index ade6851..819a2f8 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/CustomTransactionExtensions.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/CustomTransactionExtensions.kt @@ -1,12 +1,18 @@ package io.novafoundation.nova.runtime.extrinsic +import android.util.Log import io.novafoundation.nova.runtime.extrinsic.extensions.AuthorizeCall import io.novafoundation.nova.runtime.extrinsic.extensions.ChargeAssetTxPayment import io.novafoundation.nova.runtime.extrinsic.extensions.CheckAppId +import io.novafoundation.nova.runtime.extrinsic.extensions.CheckNonZeroSender +import io.novafoundation.nova.runtime.extrinsic.extensions.CheckWeight +import io.novafoundation.nova.runtime.extrinsic.extensions.WeightReclaim import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.TransactionExtension +private const val TAG = "CustomTxExtensions" + object CustomTransactionExtensions { // PezkuwiChain genesis hashes (mainnet and teyrchains) @@ -34,21 +40,33 @@ object CustomTransactionExtensions { fun defaultValues(runtime: RuntimeSnapshot): List { val extensions = mutableListOf() + val isPezkuwi = isPezkuwiChain(runtime) - // Add AuthorizeCall only for PezkuwiChain networks - if (isPezkuwiChain(runtime)) { + Log.d(TAG, "isPezkuwiChain: $isPezkuwi") + + if (isPezkuwi) { + // Pezkuwi needs: AuthorizeCall, CheckNonZeroSender, CheckWeight, WeightReclaim + // Other extensions (CheckMortality, CheckGenesis, etc.) are set in ExtrinsicBuilderFactory + Log.d(TAG, "Adding Pezkuwi extensions: AuthorizeCall, CheckNonZeroSender, CheckWeight, WeightReclaim") extensions.add(AuthorizeCall()) + extensions.add(CheckNonZeroSender()) + extensions.add(CheckWeight()) + extensions.add(WeightReclaim()) + } else { + // Other chains (Asset Hub, etc.) use ChargeAssetTxPayment and CheckAppId + Log.d(TAG, "Adding default extensions: ChargeAssetTxPayment, CheckAppId") + extensions.add(ChargeAssetTxPayment()) + extensions.add(CheckAppId()) } - extensions.add(ChargeAssetTxPayment()) - extensions.add(CheckAppId()) - return extensions } private fun isPezkuwiChain(runtime: RuntimeSnapshot): Boolean { - val genesisHash = runtime.metadata.extrinsic.signedExtensions - .any { it.id == "AuthorizeCall" } - return genesisHash + val signedExtIds = runtime.metadata.extrinsic.signedExtensions.map { it.id } + Log.d(TAG, "Metadata signed extensions: $signedExtIds") + val hasAuthorizeCall = signedExtIds.any { it == "AuthorizeCall" } + Log.d(TAG, "Has AuthorizeCall: $hasAuthorizeCall") + return hasAuthorizeCall } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt index a86b3ed..8c628ee 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/ExtrinsicBuilderFactory.kt @@ -1,12 +1,15 @@ package io.novafoundation.nova.runtime.extrinsic +import android.util.Log import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.runtime.ext.requireGenesisHash +import io.novafoundation.nova.runtime.extrinsic.extensions.PezkuwiCheckMortality import io.novafoundation.nova.runtime.extrinsic.metadata.MetadataShortenerService import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.getRuntime import io.novasama.substrate_sdk_android.extensions.fromHex +import io.novasama.substrate_sdk_android.runtime.RuntimeSnapshot import io.novasama.substrate_sdk_android.runtime.extrinsic.BatchMode import io.novasama.substrate_sdk_android.runtime.extrinsic.ExtrinsicVersion import io.novasama.substrate_sdk_android.runtime.extrinsic.builder.ExtrinsicBuilder @@ -17,6 +20,8 @@ import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtensi import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.CheckTxVersion import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.checkMetadataHash.CheckMetadataHash +private const val TAG = "ExtrinsicBuilderFactory" + class ExtrinsicBuilderFactory( private val chainRegistry: ChainRegistry, private val mortalityConstructor: MortalityConstructor, @@ -40,16 +45,33 @@ class ExtrinsicBuilderFactory( ): Sequence { val runtime = chainRegistry.getRuntime(chain.id) + // Log metadata extensions + val metadataExtensions = runtime.metadata.extrinsic.signedExtensions.map { it.id } + Log.d(TAG, "Chain: ${chain.name}, Metadata extensions: $metadataExtensions") + val mortality = mortalityConstructor.constructMortality(chain.id) val metadataProof = metadataShortenerService.generateMetadataProof(chain.id) + // Log custom extensions + val customExtensions = CustomTransactionExtensions.defaultValues(runtime).map { it.name } + Log.d(TAG, "Custom extensions to add: $customExtensions") + + val isPezkuwi = isPezkuwiChain(runtime) + Log.d(TAG, "isPezkuwiChain: $isPezkuwi") + return generateSequence { ExtrinsicBuilder( runtime = runtime, extrinsicVersion = ExtrinsicVersion.V4, batchMode = options.batchMode, ).apply { - setTransactionExtension(CheckMortality(mortality.era, mortality.blockHash.fromHex())) + // Use custom CheckMortality for Pezkuwi chains to avoid type lookup issues + if (isPezkuwi) { + Log.d(TAG, "Using PezkuwiCheckMortality for ${chain.name}") + setTransactionExtension(PezkuwiCheckMortality(mortality.era, mortality.blockHash.fromHex())) + } else { + setTransactionExtension(CheckMortality(mortality.era, mortality.blockHash.fromHex())) + } setTransactionExtension(CheckGenesis(chain.requireGenesisHash().fromHex())) setTransactionExtension(ChargeTransactionPayment(chain.additional?.defaultTip.orZero())) setTransactionExtension(CheckMetadataHash(metadataProof.checkMetadataHash)) @@ -57,7 +79,14 @@ class ExtrinsicBuilderFactory( setTransactionExtension(CheckTxVersion(metadataProof.usedVersion.transactionVersion)) CustomTransactionExtensions.defaultValues(runtime).forEach(::setTransactionExtension) + + Log.d(TAG, "All extensions set for ${chain.name}") } } } + + private fun isPezkuwiChain(runtime: RuntimeSnapshot): Boolean { + val signedExtIds = runtime.metadata.extrinsic.signedExtensions.map { it.id } + return signedExtIds.any { it == "AuthorizeCall" } + } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckNonZeroSender.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckNonZeroSender.kt new file mode 100644 index 0000000..4b31565 --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckNonZeroSender.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.runtime.extrinsic.extensions + +import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.FixedValueTransactionExtension + +/** + * Signed extension for PezkuwiChain that checks for non-zero sender. + * This extension ensures the sender is not the zero address. + * + * In the runtime, CheckNonZeroSender is defined as: + * pub struct CheckNonZeroSender(core::marker::PhantomData); + * + * It uses PhantomData internally, so it has no payload (empty encoding). + */ +class CheckNonZeroSender : FixedValueTransactionExtension( + name = "CheckNonZeroSender", + implicit = null, + explicit = null // PhantomData encodes to nothing +) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckWeight.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckWeight.kt new file mode 100644 index 0000000..1025c55 --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/CheckWeight.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.runtime.extrinsic.extensions + +import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.FixedValueTransactionExtension + +/** + * Signed extension that checks weight limits. + * This extension uses PhantomData internally, so it has no payload (empty encoding). + * + * In the runtime, CheckWeight is defined as: + * pub struct CheckWeight(core::marker::PhantomData); + */ +class CheckWeight : FixedValueTransactionExtension( + name = "CheckWeight", + implicit = null, + explicit = null // PhantomData encodes to nothing +) 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 new file mode 100644 index 0000000..c070dbf --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/PezkuwiCheckMortality.kt @@ -0,0 +1,68 @@ +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 + } + } +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/WeightReclaim.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/WeightReclaim.kt new file mode 100644 index 0000000..f8db335 --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/extensions/WeightReclaim.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.runtime.extrinsic.extensions + +import io.novasama.substrate_sdk_android.runtime.extrinsic.v5.transactionExtension.extensions.FixedValueTransactionExtension + +/** + * Signed extension for PezkuwiChain that handles weight reclamation. + * This extension reclaims unused weight after transaction execution. + * + * In the runtime, WeightReclaim is defined as: + * pub struct WeightReclaim(core::marker::PhantomData); + * + * It uses PhantomData internally, so it has no payload (empty encoding). + */ +class WeightReclaim : FixedValueTransactionExtension( + name = "WeightReclaim", + implicit = null, + explicit = null // PhantomData encodes to nothing +) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt index 0965243..85db651 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeFactory.kt @@ -47,6 +47,10 @@ class RuntimeFactory( private val gson: Gson, private val concurrencyLimit: Int = 1 ) { + companion object { + @Volatile + var lastDiagnostics: String = "not yet initialized" + } private val dispatcher = newLimitedThreadPoolExecutor(concurrencyLimit).asCoroutineDispatcher() private val semaphore = Semaphore(concurrencyLimit) @@ -88,9 +92,13 @@ class RuntimeFactory( ) } + Log.d("RuntimeFactory", "DEBUG: TypesUsage for chain $chainId = $typesUsage") + val (types, baseHash, ownHash) = when (typesUsage) { TypesUsage.BASE -> { + Log.d("RuntimeFactory", "DEBUG: Loading BASE types for $chainId") val (types, baseHash) = constructBaseTypes(typePreset) + Log.d("RuntimeFactory", "DEBUG: BASE types loaded, hash=$baseHash, typeCount=${types.size}") Triple(types, baseHash, null) } @@ -104,6 +112,15 @@ class RuntimeFactory( } val typeRegistry = TypeRegistry(types, DynamicTypeResolver(DynamicTypeResolver.DEFAULT_COMPOUND_EXTENSIONS + GenericsExtension)) + + // DEBUG: Check for ExtrinsicSignature + val hasExtrinsicSignature = typeRegistry["ExtrinsicSignature"] != null + val hasMultiSignature = typeRegistry["MultiSignature"] != null + Log.d("RuntimeFactory", "DEBUG: Chain $chainId - ExtrinsicSignature=$hasExtrinsicSignature, MultiSignature=$hasMultiSignature, typesUsage=$typesUsage, typeCount=${types.size}") + + // Store diagnostic info for error messages + lastDiagnostics = "typesUsage=$typesUsage, ExtrinsicSig=$hasExtrinsicSignature, MultiSig=$hasMultiSignature, typeCount=${types.size}" + val runtimeMetadata = VersionedRuntimeBuilder.buildMetadata(metadataReader, typeRegistry) ConstructedRuntime( @@ -154,7 +171,12 @@ class RuntimeFactory( private suspend fun constructBaseTypes(initialPreset: TypePreset): Pair { val baseTypesRaw = runCatching { runtimeFilesCache.getBaseTypes() } - .getOrElse { throw BaseTypesNotInCacheException } + .getOrElse { + Log.e("RuntimeFactory", "DEBUG: BaseTypes NOT in cache!") + throw BaseTypesNotInCacheException + } + + Log.d("RuntimeFactory", "DEBUG: BaseTypes loaded, length=${baseTypesRaw.length}, contains ExtrinsicSignature=${baseTypesRaw.contains("ExtrinsicSignature")}") val typePreset = parseBaseDefinitions(fromJson(baseTypesRaw), initialPreset) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/util/RuntimeSnapshotExt.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/util/RuntimeSnapshotExt.kt index e4d6002..b6d5cf1 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/util/RuntimeSnapshotExt.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/util/RuntimeSnapshotExt.kt @@ -5,7 +5,14 @@ import io.novasama.substrate_sdk_android.runtime.definitions.types.primitives.Fi import io.novasama.substrate_sdk_android.runtime.definitions.types.skipAliases fun RuntimeSnapshot.isEthereumAddress(): Boolean { - val addressType = typeRegistry["Address"]!!.skipAliases()!! + // Try different address type names used by different chains + val addressType = typeRegistry["Address"] + ?: typeRegistry["MultiAddress"] + ?: typeRegistry["sp_runtime::multiaddress::MultiAddress"] + ?: typeRegistry["pezsp_runtime::multiaddress::MultiAddress"] + ?: return false // If no address type found, assume not Ethereum - return addressType is FixedByteArray && addressType.length == 20 + val resolvedType = addressType.skipAliases() ?: return false + + return resolvedType is FixedByteArray && resolvedType.length == 20 } diff --git a/version.properties b/version.properties index 4cc24b9..1b32111 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -VERSION_CODE=6 \ No newline at end of file +VERSION_CODE=31 \ No newline at end of file