From bd0e3420ada212242c3f21686bd39fccbbcf0a5b Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Mon, 27 Nov 2023 16:43:44 +0100 Subject: [PATCH 001/100] Refactor local account directory. Add proxy account in db --- .../nova/core_db/dao/Helpers.kt | 4 +- .../migrations/BetterChainDiffingTest_8_9.kt | 2 +- .../nova/core_db/AppDatabase.kt | 8 ++- .../converters/MetaAccountTypeConverters.kt | 2 +- .../converters/ProxyAccountConverters.kt | 26 ++++++++++ .../nova/core_db/dao/MetaAccountDao.kt | 8 +-- .../migrations/14_15_AddMetaAccountType.kt | 2 +- .../nova/core_db/model/BalanceLockLocal.kt | 2 +- .../core_db/model/ExternalBalanceLocal.kt | 2 +- .../model/StakingDashboardItemLocal.kt | 2 +- .../model/WalletConnectPairingLocal.kt | 2 +- .../model/chain/account/ChainAccountLocal.kt | 34 +++++++++++++ .../chain/account/JoinedMetaAccountInfo.kt | 19 +++++++ .../chain/{ => account}/MetaAccountLocal.kt | 51 ++----------------- .../model/chain/account/ProxyAccountLocal.kt | 35 +++++++++++++ .../data/mappers/Mappers.kt | 6 +-- .../data/repository/ParitySignerRepository.kt | 2 +- .../data/repository/WatchOnlyRepository.kt | 4 +- .../datasource/AccountDataSourceImpl.kt | 6 +-- .../data/repository/LedgerRepository.kt | 4 +- 20 files changed, 147 insertions(+), 74 deletions(-) create mode 100644 core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt create mode 100644 core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ChainAccountLocal.kt create mode 100644 core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/JoinedMetaAccountInfo.kt rename core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/{ => account}/MetaAccountLocal.kt (51%) create mode 100644 core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt diff --git a/core-db/src/androidTest/java/io/novafoundation/nova/core_db/dao/Helpers.kt b/core-db/src/androidTest/java/io/novafoundation/nova/core_db/dao/Helpers.kt index 1ba32b9d99..098334b9e0 100644 --- a/core-db/src/androidTest/java/io/novafoundation/nova/core_db/dao/Helpers.kt +++ b/core-db/src/androidTest/java/io/novafoundation/nova/core_db/dao/Helpers.kt @@ -4,12 +4,12 @@ import io.novafoundation.nova.common.utils.CollectionDiffer import io.novafoundation.nova.core.model.CryptoType import io.novafoundation.nova.core_db.model.CurrencyLocal import io.novafoundation.nova.core_db.model.chain.AssetSourceLocal -import io.novafoundation.nova.core_db.model.chain.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal import io.novafoundation.nova.core_db.model.chain.ChainLocal import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal import io.novafoundation.nova.core_db.model.chain.JoinedChainInfo -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal fun createTestChain( id: String, diff --git a/core-db/src/androidTest/java/io/novafoundation/nova/core_db/migrations/BetterChainDiffingTest_8_9.kt b/core-db/src/androidTest/java/io/novafoundation/nova/core_db/migrations/BetterChainDiffingTest_8_9.kt index cca240e69b..370dacd76b 100644 --- a/core-db/src/androidTest/java/io/novafoundation/nova/core_db/migrations/BetterChainDiffingTest_8_9.kt +++ b/core-db/src/androidTest/java/io/novafoundation/nova/core_db/migrations/BetterChainDiffingTest_8_9.kt @@ -8,7 +8,7 @@ import io.novafoundation.nova.core_db.converters.CryptoTypeConverters import io.novafoundation.nova.core_db.dao.assetOf import io.novafoundation.nova.core_db.dao.chainOf import io.novafoundation.nova.core_db.dao.testMetaAccount -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import org.junit.Assert.assertEquals import org.junit.Test import java.math.BigInteger diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt index eaa7263da9..c76a691c01 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt @@ -16,6 +16,7 @@ import io.novafoundation.nova.core_db.converters.MetaAccountTypeConverters import io.novafoundation.nova.core_db.converters.NetworkTypeConverters import io.novafoundation.nova.core_db.converters.NftTypeConverters import io.novafoundation.nova.core_db.converters.OperationConverters +import io.novafoundation.nova.core_db.converters.ProxyAccountConverters import io.novafoundation.nova.core_db.dao.AccountDao import io.novafoundation.nova.core_db.dao.AccountStakingDao import io.novafoundation.nova.core_db.dao.AssetDao @@ -115,15 +116,16 @@ import io.novafoundation.nova.core_db.model.StakingRewardPeriodLocal import io.novafoundation.nova.core_db.model.StorageEntryLocal import io.novafoundation.nova.core_db.model.TokenLocal import io.novafoundation.nova.core_db.model.TotalRewardLocal +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.WalletConnectPairingLocal -import io.novafoundation.nova.core_db.model.chain.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal import io.novafoundation.nova.core_db.model.chain.ChainExplorerLocal import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal import io.novafoundation.nova.core_db.model.chain.ChainLocal import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal import io.novafoundation.nova.core_db.model.chain.ChainRuntimeInfoLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal import io.novafoundation.nova.core_db.model.operation.DirectRewardTypeLocal import io.novafoundation.nova.core_db.model.operation.ExtrinsicTypeLocal import io.novafoundation.nova.core_db.model.operation.OperationBaseLocal @@ -170,6 +172,7 @@ import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal StakingDashboardItemLocal::class, StakingRewardPeriodLocal::class, ExternalBalanceLocal::class, + ProxyAccountLocal::class ], ) @TypeConverters( @@ -184,6 +187,7 @@ import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal ExternalApiConverters::class, ChainConverters::class, ExternalBalanceTypeConverters::class, + ProxyAccountConverters::class ) abstract class AppDatabase : RoomDatabase() { diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/MetaAccountTypeConverters.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/MetaAccountTypeConverters.kt index ee18fe7fe2..59ec7e0acc 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/MetaAccountTypeConverters.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/MetaAccountTypeConverters.kt @@ -1,7 +1,7 @@ package io.novafoundation.nova.core_db.converters import androidx.room.TypeConverter -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal class MetaAccountTypeConverters { diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt new file mode 100644 index 0000000000..821cffde4d --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt @@ -0,0 +1,26 @@ +package io.novafoundation.nova.core_db.converters + +import androidx.room.TypeConverter +import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal + +class ProxyAccountConverters { + @TypeConverter + fun fromRightType(type: ProxyAccountLocal.RightType): String { + return type.name + } + + @TypeConverter + fun toRightType(name: String): ProxyAccountLocal.RightType { + return ProxyAccountLocal.RightType.valueOf(name) + } + + @TypeConverter + fun fromStatusType(type: ProxyAccountLocal.Status): String { + return type.name + } + + @TypeConverter + fun toStatusType(name: String): ProxyAccountLocal.Status { + return ProxyAccountLocal.Status.valueOf(name) + } +} diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index a3d246da31..ba38ca316e 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -6,10 +6,10 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.Transaction import androidx.room.Update -import io.novafoundation.nova.core_db.model.chain.ChainAccountLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountPositionUpdate -import io.novafoundation.nova.core_db.model.chain.RelationJoinedMetaAccountInfo +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountPositionUpdate +import io.novafoundation.nova.core_db.model.chain.account.RelationJoinedMetaAccountInfo import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.flow.Flow import org.intellij.lang.annotations.Language diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/14_15_AddMetaAccountType.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/14_15_AddMetaAccountType.kt index dbc4655f08..64aa2b0bd5 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/14_15_AddMetaAccountType.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/14_15_AddMetaAccountType.kt @@ -3,7 +3,7 @@ package io.novafoundation.nova.core_db.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import io.novafoundation.nova.core_db.converters.MetaAccountTypeConverters -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal val AddMetaAccountType_14_15 = object : Migration(14, 15) { diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/BalanceLockLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/BalanceLockLocal.kt index 72e401f2b1..d3602fc26c 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/BalanceLockLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/BalanceLockLocal.kt @@ -4,7 +4,7 @@ import androidx.room.Entity import androidx.room.ForeignKey import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal import io.novafoundation.nova.core_db.model.chain.ChainLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import java.math.BigInteger @Entity( diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/ExternalBalanceLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/ExternalBalanceLocal.kt index c2206d7d8d..1645496b03 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/ExternalBalanceLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/ExternalBalanceLocal.kt @@ -3,7 +3,7 @@ package io.novafoundation.nova.core_db.model import androidx.room.Entity import androidx.room.ForeignKey import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import java.math.BigInteger @Entity( diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/StakingDashboardItemLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/StakingDashboardItemLocal.kt index a824d3cfcc..b07ba44341 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/StakingDashboardItemLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/StakingDashboardItemLocal.kt @@ -5,7 +5,7 @@ import androidx.room.Entity import androidx.room.ForeignKey import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal import io.novafoundation.nova.core_db.model.chain.ChainLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import jp.co.soramitsu.fearless_utils.runtime.AccountId import java.math.BigInteger diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/WalletConnectPairingLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/WalletConnectPairingLocal.kt index e6f5723e5c..0035e68b16 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/WalletConnectPairingLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/WalletConnectPairingLocal.kt @@ -4,7 +4,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.PrimaryKey -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal @Entity( tableName = "wallet_connect_pairings", diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ChainAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ChainAccountLocal.kt new file mode 100644 index 0000000000..31abe03ef1 --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ChainAccountLocal.kt @@ -0,0 +1,34 @@ +package io.novafoundation.nova.core_db.model.chain.account + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import io.novafoundation.nova.core.model.CryptoType + +@Entity( + tableName = "chain_accounts", + foreignKeys = [ + // no foreign key for `chainId` since we do not want ChainAccounts to be deleted or modified when chain is deleted + // but rather keep it in db in case future UI will show them somehow + + ForeignKey( + parentColumns = ["id"], + childColumns = ["metaId"], + entity = MetaAccountLocal::class, + onDelete = ForeignKey.CASCADE + ), + ], + indices = [ + Index(value = ["chainId"]), + Index(value = ["metaId"]), + Index(value = ["accountId"]), + ], + primaryKeys = ["metaId", "chainId"] +) +class ChainAccountLocal( + val metaId: Long, + val chainId: String, + val publicKey: ByteArray?, + val accountId: ByteArray, + val cryptoType: CryptoType?, +) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/JoinedMetaAccountInfo.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/JoinedMetaAccountInfo.kt new file mode 100644 index 0000000000..2504b7a1f7 --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/JoinedMetaAccountInfo.kt @@ -0,0 +1,19 @@ +package io.novafoundation.nova.core_db.model.chain.account + +import androidx.room.Embedded +import androidx.room.Relation + +interface JoinedMetaAccountInfo { + + val metaAccount: MetaAccountLocal + + val chainAccounts: List +} + +class RelationJoinedMetaAccountInfo( + @Embedded + override val metaAccount: MetaAccountLocal, + + @Relation(parentColumn = "id", entityColumn = "metaId", entity = ChainAccountLocal::class) + override val chainAccounts: List, +) : JoinedMetaAccountInfo diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/MetaAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt similarity index 51% rename from core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/MetaAccountLocal.kt rename to core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt index b2cdbfe306..85d6003c2c 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/MetaAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt @@ -1,11 +1,8 @@ -package io.novafoundation.nova.core_db.model.chain +package io.novafoundation.nova.core_db.model.chain.account -import androidx.room.Embedded import androidx.room.Entity -import androidx.room.ForeignKey import androidx.room.Index import androidx.room.PrimaryKey -import androidx.room.Relation import io.novafoundation.nova.core.model.CryptoType @Entity( @@ -21,6 +18,7 @@ class MetaAccountLocal( val substrateAccountId: ByteArray?, val ethereumPublicKey: ByteArray?, val ethereumAddress: ByteArray?, + val delegate: String?, val name: String, val isSelected: Boolean, val position: Int, @@ -49,53 +47,10 @@ class MetaAccountLocal( var id: Long = 0 enum class Type { - SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, POLKADOT_VAULT + SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, POLKADOT_VAULT, PROXY } } -@Entity( - tableName = "chain_accounts", - foreignKeys = [ - // no foreign key for `chainId` since we do not want ChainAccounts to be deleted or modified when chain is deleted - // but rather keep it in db in case future UI will show them somehow - - ForeignKey( - parentColumns = ["id"], - childColumns = ["metaId"], - entity = MetaAccountLocal::class, - onDelete = ForeignKey.CASCADE - ), - ], - indices = [ - Index(value = ["chainId"]), - Index(value = ["metaId"]), - Index(value = ["accountId"]), - ], - primaryKeys = ["metaId", "chainId"] -) -class ChainAccountLocal( - val metaId: Long, - val chainId: String, - val publicKey: ByteArray?, - val accountId: ByteArray, - val cryptoType: CryptoType?, -) - -interface JoinedMetaAccountInfo { - - val metaAccount: MetaAccountLocal - - val chainAccounts: List -} - -class RelationJoinedMetaAccountInfo( - @Embedded - override val metaAccount: MetaAccountLocal, - - @Relation(parentColumn = "id", entityColumn = "metaId", entity = ChainAccountLocal::class) - override val chainAccounts: List, -) : JoinedMetaAccountInfo - class MetaAccountPositionUpdate( val id: Long, val position: Int diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt new file mode 100644 index 0000000000..5cd5ea2f78 --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt @@ -0,0 +1,35 @@ +package io.novafoundation.nova.core_db.model.chain.account + +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Index +import io.novafoundation.nova.core.model.CryptoType + +@Entity( + tableName = "proxy_accounts", + foreignKeys = [ + ForeignKey( + parentColumns = ["id"], + childColumns = ["metaId"], + entity = MetaAccountLocal::class, + onDelete = ForeignKey.CASCADE + ), + ], + primaryKeys = ["metaId", "delegatorAccountId", "chainId", "rightType"] +) +class ProxyAccountLocal( + val metaId: Long, + val chainId: String, + val delegatorAccountId: ByteArray, + val rightType: RightType, + val status: Status, +) { + + enum class RightType { + ANY, UNKNOWN //TODO add more + } + + enum class Status { + ACTIVE, DEACTIVATED + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index 5637e7af57..c0b25284e2 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -7,9 +7,9 @@ import io.novafoundation.nova.core.model.Node import io.novafoundation.nova.core.model.Node.NetworkType import io.novafoundation.nova.core_db.dao.MetaAccountWithBalanceLocal import io.novafoundation.nova.core_db.model.NodeLocal -import io.novafoundation.nova.core_db.model.chain.ChainAccountLocal -import io.novafoundation.nova.core_db.model.chain.JoinedMetaAccountInfo -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.JoinedMetaAccountInfo +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt index 2af2d2bf52..99e9288673 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt @@ -2,7 +2,7 @@ package io.novafoundation.nova.feature_account_impl.data.repository import io.novafoundation.nova.core.model.CryptoType import io.novafoundation.nova.core_db.dao.MetaAccountDao -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant import jp.co.soramitsu.fearless_utils.runtime.AccountId diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt index ab4d1b5dde..bf2d673437 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt @@ -1,8 +1,8 @@ package io.novafoundation.nova.feature_account_impl.data.repository import io.novafoundation.nova.core_db.dao.MetaAccountDao -import io.novafoundation.nova.core_db.model.chain.ChainAccountLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.runtime.AccountId diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index a27b0c7207..b57ee477b4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -15,9 +15,9 @@ import io.novafoundation.nova.core.model.Language import io.novafoundation.nova.core.model.Node import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.core_db.dao.NodeDao -import io.novafoundation.nova.core_db.model.chain.ChainAccountLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountPositionUpdate +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountPositionUpdate import io.novafoundation.nova.feature_account_api.domain.model.Account import io.novafoundation.nova.feature_account_api.domain.model.AuthType import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt index 50bf0e2f3a..fb61f93579 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt @@ -3,8 +3,8 @@ package io.novafoundation.nova.feature_ledger_impl.data.repository import io.novafoundation.nova.common.data.mappers.mapEncryptionToCryptoType import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 import io.novafoundation.nova.core_db.dao.MetaAccountDao -import io.novafoundation.nova.core_db.model.chain.ChainAccountLocal -import io.novafoundation.nova.core_db.model.chain.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry From 1d09962b2bdef6ba9d6d3b6531a378325f2c974b Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Mon, 27 Nov 2023 17:05:59 +0100 Subject: [PATCH 002/100] Add migration for proxy accounts --- .../nova/core_db/AppDatabase.kt | 5 ++-- .../migrations/53_54_AddProxyAccount.kt | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt index c76a691c01..820dabc989 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt @@ -65,6 +65,7 @@ import io.novafoundation.nova.core_db.migrations.AddMetaAccountType_14_15 import io.novafoundation.nova.core_db.migrations.AddNfts_5_6 import io.novafoundation.nova.core_db.migrations.AddNodeSelectionStrategyField_38_39 import io.novafoundation.nova.core_db.migrations.AddPoolIdToOperations_46_47 +import io.novafoundation.nova.core_db.migrations.AddProxyAccount_53_54 import io.novafoundation.nova.core_db.migrations.AddRewardAccountToStakingDashboard_43_44 import io.novafoundation.nova.core_db.migrations.AddRuntimeFlagToChains_36_37 import io.novafoundation.nova.core_db.migrations.AddSitePhishing_6_7 @@ -134,7 +135,7 @@ import io.novafoundation.nova.core_db.model.operation.SwapTypeLocal import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal @Database( - version = 53, + version = 54, entities = [ AccountLocal::class, NodeLocal::class, @@ -224,7 +225,7 @@ abstract class AppDatabase : RoomDatabase() { .addMigrations(AddRewardAccountToStakingDashboard_43_44, AddStakingTypeToTotalRewards_44_45, AddExternalBalances_45_46) .addMigrations(AddPoolIdToOperations_46_47, AddEventIdToOperation_47_48, AddSwapOption_48_49) .addMigrations(RefactorOperations_49_50, AddTransactionVersionToRuntime_50_51, AddBalanceModesToAssets_51_52) - .addMigrations(ChangeSessionTopicToParing_52_53) + .addMigrations(ChangeSessionTopicToParing_52_53, AddProxyAccount_53_54) .build() } return instance!! diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt new file mode 100644 index 0000000000..b12851add8 --- /dev/null +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt @@ -0,0 +1,24 @@ +package io.novafoundation.nova.core_db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import io.novafoundation.nova.core_db.converters.AssetConverters +import io.novafoundation.nova.core_db.model.AssetLocal + +val AddProxyAccount_53_54 = object : Migration(53, 54) { + + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + """ + CREATE TABLE IF NOT EXISTS `proxy_accounts` ( + `metaId` INTEGER NOT NULL, + `chainId` TEXT NOT NULL, + `delegatorAccountId` BLOB NOT NULL, + `rightType` TEXT NOT NULL, + `status` TEXT NOT NULL, + PRIMARY KEY(`metaId`, `delegatorAccountId`, `chainId`, `rightType`), + FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) + """.trimMargin() + ) + } +} From e5e18a0135ef2fb7e499b8bd90f8e1d79f6630ac Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Mon, 27 Nov 2023 17:22:38 +0100 Subject: [PATCH 003/100] Minor changes --- .../nova/core_db/model/chain/account/MetaAccountLocal.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt index 85d6003c2c..f6a8f7590c 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt @@ -18,7 +18,6 @@ class MetaAccountLocal( val substrateAccountId: ByteArray?, val ethereumPublicKey: ByteArray?, val ethereumAddress: ByteArray?, - val delegate: String?, val name: String, val isSelected: Boolean, val position: Int, From 839dc39efd545e5e9f8d29eb9528ab24c61f75a0 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 28 Nov 2023 16:27:51 +0100 Subject: [PATCH 004/100] Proxies syncing in progress --- .../nova/app/root/di/RootDependencies.kt | 9 ++++ .../nova/app/root/di/RootFeatureModule.kt | 7 ++- .../nova/app/root/domain/RootInteractor.kt | 9 +++- .../app/root/presentation/RootViewModel.kt | 7 +++ .../nova/common/utils/FearlessLibExt.kt | 2 + .../migrations/53_54_AddProxyAccount.kt | 2 + .../nova/core_db/model/chain/ChainLocal.kt | 2 + .../data/proxy/ProxySyncService.kt | 10 ++++ .../data/repository/ProxyRepository.kt | 8 ++++ .../di/AccountFeatureApi.kt | 3 ++ .../domain/model/MetaAccount.kt | 13 +++++ .../data/mappers/Mappers.kt | 23 +++++---- .../data/proxy/RealProxySyncService.kt | 47 +++++++++++++++++++ .../data/repository/RealProxyRepository.kt | 39 +++++++++++++++ .../di/AccountFeatureModule.kt | 31 ++++++++++++ .../chain/mappers/ChainMappers.kt | 1 + .../mappers/RemoteToLocalChainMappers.kt | 2 + .../runtime/multiNetwork/chain/model/Chain.kt | 1 + 18 files changed, 204 insertions(+), 12 deletions(-) create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt index 90e93819e7..7dd664c80d 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.common.utils.systemCall.SystemCallExecutor +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem import io.novafoundation.nova.feature_crowdloan_api.data.repository.CrowdloanRepository @@ -58,6 +59,14 @@ interface RootDependencies { fun rootScope(): RootScope + fun proxySyncService(): ProxySyncService + + fun governanceStateUpdater(): MutableGovernanceState + + fun dappMetadataRepository(): DAppMetadataRepository + + fun encryptionDefaults(): EncryptionDefaults + val systemCallExecutor: SystemCallExecutor val contextManager: ContextManager diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureModule.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureModule.kt index 5c041c4417..8d0266f744 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureModule.kt @@ -4,6 +4,7 @@ import dagger.Module import dagger.Provides import io.novafoundation.nova.app.root.domain.RootInteractor import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository @@ -16,12 +17,14 @@ class RootFeatureModule { fun provideRootInteractor( walletRepository: WalletRepository, accountRepository: AccountRepository, - balancesUpdateSystem: BalancesUpdateSystem + balancesUpdateSystem: BalancesUpdateSystem, + proxySyncService: ProxySyncService ): RootInteractor { return RootInteractor( updateSystem = balancesUpdateSystem, walletRepository = walletRepository, - accountRepository = accountRepository + accountRepository = accountRepository, + proxySyncService = proxySyncService ) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt b/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt index 43a3942795..2348022cf8 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt @@ -1,7 +1,9 @@ package io.novafoundation.nova.app.root.domain import io.novafoundation.nova.core.updater.Updater +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_impl.data.proxy.RealProxySyncService import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem import io.novafoundation.nova.feature_buy_impl.domain.providers.ExternalProvider import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository @@ -10,7 +12,8 @@ import kotlinx.coroutines.flow.Flow class RootInteractor( private val updateSystem: BalancesUpdateSystem, private val walletRepository: WalletRepository, - private val accountRepository: AccountRepository + private val accountRepository: AccountRepository, + private val proxySyncService: ProxySyncService ) { fun runBalancesUpdate(): Flow = updateSystem.start() @@ -30,4 +33,8 @@ class RootInteractor( suspend fun isPinCodeSet(): Boolean { return accountRepository.isCodeSet() } + + suspend fun syncProxies() { + proxySyncService.startSyncing() + } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt index 1a6a9c8c61..e995493f2f 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.common.sequrity.SafeModeService import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.core.updater.Updater +import io.novafoundation.nova.feature_account_impl.data.proxy.RealProxySyncService import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor @@ -55,6 +56,8 @@ class RootViewModel( checkForUpdates() + syncProxies() + syncCurrencies() syncWalletConnectSessions() @@ -84,6 +87,10 @@ class RootViewModel( launch { currencyInteractor.syncCurrencies() } } + private fun syncProxies() { + launch { interactor.syncProxies() } + } + private fun handleUpdatesSideEffect(sideEffect: Updater.SideEffect) { // pass } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index ea6ad0c683..8d5d4c7a65 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -344,4 +344,6 @@ object Modules { const val ASSET_TX_PAYMENT = "AssetTxPayment" const val UTILITY = "Utility" + + const val PROXY = "Proxy" } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt index b12851add8..62f98dab38 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt @@ -20,5 +20,7 @@ val AddProxyAccount_53_54 = object : Migration(53, 54) { FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) """.trimMargin() ) + + database.execSQL("ALTER TABLE 'chains' ADD COLUMN `supportProxy` INTEGER NOT NULL DEFAULT 0") } } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/ChainLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/ChainLocal.kt index 5eb803416c..2ac7c7f44f 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/ChainLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/ChainLocal.kt @@ -22,6 +22,8 @@ data class ChainLocal( @ColumnInfo(defaultValue = "1") val hasSubstrateRuntime: Boolean, val hasCrowdloans: Boolean, + @ColumnInfo(defaultValue = "0") + val supportProxy: Boolean, val swap: String, val governance: String, val additional: String?, diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt new file mode 100644 index 0000000000..75d61fe878 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt @@ -0,0 +1,10 @@ +package io.novafoundation.nova.feature_account_api.data.proxy + +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount + +interface ProxySyncService { + + suspend fun startSyncing() + + suspend fun syncForMetaAccount(metaAccount: MetaAccount) +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt new file mode 100644 index 0000000000..8bc37c793d --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.feature_account_api.data.repository + +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId + +interface ProxyRepository { + suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, chainAccounts: List) +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt index b9d24cc702..091ae3db47 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt @@ -5,6 +5,7 @@ import io.novafoundation.nova.common.sequrity.TwoFactorVerificationExecutor import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider @@ -69,6 +70,8 @@ interface AccountFeatureApi { @OnChainIdentity fun onChainIdentityProvider(): IdentityProvider + fun proxySyncService(): ProxySyncService + val evmTransactionService: EvmTransactionService val identityMixinFactory: IdentityMixin.Factory diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt index c69e19405c..1b436d8086 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt @@ -60,6 +60,7 @@ fun LightMetaAccount( class MetaAccount( override val id: Long, val chainAccounts: Map, + val proxies: List, override val substratePublicKey: ByteArray?, override val substrateCryptoType: CryptoType?, override val substrateAccountId: ByteArray?, @@ -77,6 +78,17 @@ class MetaAccount( val accountId: ByteArray, val cryptoType: CryptoType?, ) + + class ProxyAccount( + val metaId: Long, + val proxyAccountId: ByteArray, + val proxyType: ProxyType, + ) { + + enum class ProxyType { + ANY, UNSUPPORTED + } + } } fun MetaAccount.hasAccountIn(chain: Chain) = when { @@ -141,6 +153,7 @@ fun MetaAccount.multiChainEncryptionIn(chain: Chain): MultiChainEncryption? { MultiChainEncryption.substrateFrom(cryptoType) } } + chain.isEthereumBased -> MultiChainEncryption.Ethereum else -> substrateCryptoType?.let(MultiChainEncryption.Companion::substrateFrom) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index c0b25284e2..00d807d5de 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -38,19 +38,21 @@ fun mapCryptoTypeToCryptoTypeModel( ): CryptoTypeModel { val name = when (encryptionType) { CryptoType.SR25519 -> "${resourceManager.getString(R.string.sr25519_selection_title)} ${ - resourceManager.getString( - R.string.sr25519_selection_subtitle - ) + resourceManager.getString( + R.string.sr25519_selection_subtitle + ) }" + CryptoType.ED25519 -> "${resourceManager.getString(R.string.ed25519_selection_title)} ${ - resourceManager.getString( - R.string.ed25519_selection_subtitle - ) + resourceManager.getString( + R.string.ed25519_selection_subtitle + ) }" + CryptoType.ECDSA -> "${resourceManager.getString(R.string.ecdsa_selection_title)} ${ - resourceManager.getString( - R.string.ecdsa_selection_subtitle - ) + resourceManager.getString( + R.string.ecdsa_selection_subtitle + ) }" } @@ -92,6 +94,7 @@ private fun mapMetaAccountTypeFromLocal(local: MetaAccountLocal.Type): LightMeta MetaAccountLocal.Type.PARITY_SIGNER -> LightMetaAccount.Type.PARITY_SIGNER MetaAccountLocal.Type.LEDGER -> LightMetaAccount.Type.LEDGER MetaAccountLocal.Type.POLKADOT_VAULT -> LightMetaAccount.Type.POLKADOT_VAULT + MetaAccountLocal.Type.PROXY -> TODO() } } @@ -128,6 +131,7 @@ fun mapMetaAccountLocalToMetaAccount( MetaAccount( id = id, chainAccounts = chainAccounts, + proxies = listOf(), // TODO substratePublicKey = substratePublicKey, substrateCryptoType = substrateCryptoType, substrateAccountId = substrateAccountId, @@ -168,6 +172,7 @@ fun mapAddAccountPayloadToAddAccountType( AddAccountType.MetaAccount(accountNameState.value) } + is AddAccountPayload.ChainAccount -> AddAccountType.ChainAccount(payload.chainId, payload.metaId) } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt new file mode 100644 index 0000000000..3943341bde --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -0,0 +1,47 @@ +package io.novafoundation.nova.feature_account_impl.data.proxy + +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.chainsById + +class RealProxySyncService( + private val chainRegistry: ChainRegistry, + private val proxyRepository: ProxyRepository, + private val accounRepository: AccountRepository +) : ProxySyncService { + + override suspend fun startSyncing() { + if (accounRepository.metaAccountFlow()) + val metaAccounts = getMetaAccount() + + val chainIdToChainAcconts = metaAccounts.flatMap { it.chainAccounts.values } + .groupBy { it.chainId } + + val supportedProxyChains = getSupportedProxyChains() + supportedProxyChains.get("")!!. + + for ((chainId, chainAccounts) in chainIdToChainAcconts) { + proxyRepository.getProxyDelegatorsForAccounts(chainId, chainAccounts) + } + } + + override suspend fun syncForMetaAccount(metaAccount: MetaAccount) { + TODO("provide updater to sync proxy delegators for new added accounts") + } + + private suspend fun getMetaAccount(): List { + return accounRepository.allMetaAccounts() + .filter { it.type != LightMetaAccount.Type.WATCH_ONLY || true/*it.type != LightMetaAccount.Type.PROXY*/ } + } + + private suspend fun getSupportedProxyChains(): Map { + return chainRegistry.chainsById() + .filterValues { it.supportProxy } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt new file mode 100644 index 0000000000..f376650f63 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -0,0 +1,39 @@ +package io.novafoundation.nova.feature_account_impl.data.repository + +import io.novafoundation.nova.common.data.network.runtime.binding.castToList +import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct +import io.novafoundation.nova.common.data.network.runtime.binding.getTyped +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount.ProxyAccount +import io.novafoundation.nova.runtime.call.RuntimeCallsApi +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.storage.source.StorageDataSource +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.metadata.module +import jp.co.soramitsu.fearless_utils.runtime.metadata.storage + +class RealProxyRepository( + private val remoteSource: StorageDataSource +) : ProxyRepository { + + override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, chainAccounts: List) { + val allProxiesOnChain = remoteSource.query(chainId) { + runtime.metadata.module(Modules.PROXY) + .storage("Proxies") + .entries { result, storageKeyComponent -> + storageKeyComponent.values + bindProxyAccounts(result) + } + } + } + + private fun bindProxyAccounts(dynamicInstance: Any?): List> { + val delegates = dynamicInstance.castToList() + val proxies = delegates[0].castToStruct() + val proxyAccountId: ByteArray = proxies.getTyped("delegate") + val proxyType: String = proxies.getTyped("proxyType") + return listOf() + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index bb75005254..b01309fab6 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -22,7 +22,9 @@ import io.novafoundation.nova.core_db.dao.NodeDao import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository @@ -46,9 +48,11 @@ import io.novafoundation.nova.feature_account_impl.data.ethereum.transaction.Rea import io.novafoundation.nova.feature_account_impl.data.extrinsic.RealExtrinsicService import io.novafoundation.nova.feature_account_impl.data.network.blockchain.AccountSubstrateSource import io.novafoundation.nova.feature_account_impl.data.network.blockchain.AccountSubstrateSourceImpl +import io.novafoundation.nova.feature_account_impl.data.proxy.RealProxySyncService import io.novafoundation.nova.feature_account_impl.data.repository.AccountRepositoryImpl import io.novafoundation.nova.feature_account_impl.data.repository.AddAccountRepository import io.novafoundation.nova.feature_account_impl.data.repository.RealOnChainIdentityRepository +import io.novafoundation.nova.feature_account_impl.data.repository.RealProxyRepository import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSourceImpl import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.AccountDataMigration @@ -89,6 +93,33 @@ import javax.inject.Named @Module(includes = [SignersModule::class, WatchOnlyModule::class, ParitySignerModule::class, IdentityProviderModule::class]) class AccountFeatureModule { + @Provides + @FeatureScope + fun provideProxyRepository( + @Named(REMOTE_STORAGE_SOURCE) storageDataSource: StorageDataSource + ): ProxyRepository = RealProxyRepository(storageDataSource) + + @Provides + @FeatureScope + fun provideProxySyncService( + chainRegistry: ChainRegistry, + proxyRepository: ProxyRepository, + accounRepository: AccountRepository + ): ProxySyncService = RealProxySyncService( + chainRegistry, + proxyRepository, + accounRepository + ) + + @Provides + @FeatureScope + fun provideEncryptionDefaults(): EncryptionDefaults = EncryptionDefaults( + substrateCryptoType = CryptoType.SR25519, + substrateDerivationPath = "", + ethereumCryptoType = mapEncryptionToCryptoType(MultiChainEncryption.Ethereum.encryptionType), + ethereumDerivationPath = BIP32JunctionDecoder.DEFAULT_DERIVATION_PATH + ) + @Provides @FeatureScope fun provideExtrinsicService( diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/ChainMappers.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/ChainMappers.kt index 2523e3164f..98765bdb56 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/ChainMappers.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/ChainMappers.kt @@ -308,6 +308,7 @@ fun mapChainLocalToChain(chainLocal: JoinedChainInfo, gson: Gson): Chain { isEthereumBased = isEthereumBased, isTestNet = isTestNet, hasCrowdloans = hasCrowdloans, + supportProxy = supportProxy, hasSubstrateRuntime = hasSubstrateRuntime, governance = mapGovernanceListFromLocal(governance), swap = mapSwapListFromLocal(swap), diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/RemoteToLocalChainMappers.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/RemoteToLocalChainMappers.kt index 8d50bf7078..47bdef21a5 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/RemoteToLocalChainMappers.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/mappers/RemoteToLocalChainMappers.kt @@ -19,6 +19,7 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.remote.model.ChainRemot private const val ETHEREUM_OPTION = "ethereumBased" private const val CROWDLOAN_OPTION = "crowdloans" private const val TESTNET_OPTION = "testnet" +private const val PROXY_OPTION = "proxy" private const val SWAP_HUB = "swap-hub" private const val NO_SUBSTRATE_RUNTIME = "noSubstrateRuntime" @@ -64,6 +65,7 @@ fun mapRemoteChainToLocal( isEthereumBased = ETHEREUM_OPTION in optionsOrEmpty, isTestNet = TESTNET_OPTION in optionsOrEmpty, hasCrowdloans = CROWDLOAN_OPTION in optionsOrEmpty, + supportProxy = PROXY_OPTION in optionsOrEmpty, hasSubstrateRuntime = NO_SUBSTRATE_RUNTIME !in optionsOrEmpty, governance = mapGovernanceRemoteOptionsToLocal(optionsOrEmpty), swap = mapSwapRemoteOptionsToLocal(optionsOrEmpty), diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt index 7a13baa6eb..69e225d0ea 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/chain/model/Chain.kt @@ -28,6 +28,7 @@ data class Chain( val isTestNet: Boolean, val hasSubstrateRuntime: Boolean, val hasCrowdloans: Boolean, + val supportProxy: Boolean, val governance: List, val swap: List, val parentId: String?, From e5b64ca73c0983d976d5c233093387090361eaf4 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 30 Nov 2023 11:28:14 +0100 Subject: [PATCH 005/100] Implemented proxies sync --- .../app/root/presentation/RootViewModel.kt | 3 +- .../converters/ProxyAccountConverters.kt | 6 +- .../nova/core_db/dao/MetaAccountDao.kt | 15 ++ .../migrations/53_54_AddProxyAccount.kt | 6 +- .../model/chain/account/MetaAccountLocal.kt | 2 +- .../model/chain/account/ProxyAccountLocal.kt | 20 ++- .../data/model/ProxiedWithProxies.kt | 16 ++ .../data/repository/ProxyRepository.kt | 6 +- .../interfaces/SelectedAccountUseCase.kt | 2 + .../domain/model/MetaAccount.kt | 2 +- .../domain/model/MetaAccountId.kt | 5 + .../data/mappers/Mappers.kt | 2 +- .../data/proxy/RealProxySyncService.kt | 146 ++++++++++++++++-- .../data/repository/RealProxyRepository.kt | 83 ++++++++-- .../data/signer/RealSignerProvider.kt | 1 + .../di/AccountFeatureModule.kt | 10 +- .../MetaAccountGroupingInteractorImpl.kt | 3 +- .../MetaAccountTypePresentationMapper.kt | 2 + .../details/AccountDetailsViewModel.kt | 9 +- .../AddAccountLauncherProvider.kt | 2 +- .../source/query/BaseStorageQueryContext.kt | 26 +++- .../source/query/StorageQueryContext.kt | 6 +- 22 files changed, 308 insertions(+), 65 deletions(-) create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxies.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountId.kt diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt index 53910b9ab6..e394a94894 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt @@ -23,6 +23,7 @@ import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInt import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection.ExternalRequirement +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn @@ -111,7 +112,7 @@ class RootViewModel( } private fun syncProxies() { - launch { interactor.syncProxies() } + launch(Dispatchers.Default) { interactor.syncProxies() } } private fun handleUpdatesSideEffect(sideEffect: Updater.SideEffect) { diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt index 821cffde4d..846fbe3089 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt @@ -5,13 +5,13 @@ import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal class ProxyAccountConverters { @TypeConverter - fun fromRightType(type: ProxyAccountLocal.RightType): String { + fun fromRightType(type: ProxyAccountLocal.ProxyType): String { return type.name } @TypeConverter - fun toRightType(name: String): ProxyAccountLocal.RightType { - return ProxyAccountLocal.RightType.valueOf(name) + fun toRightType(name: String): ProxyAccountLocal.ProxyType { + return ProxyAccountLocal.ProxyType.valueOf(name) } @TypeConverter diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index c7d85692c4..4f6c6f6e16 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -9,6 +9,7 @@ import androidx.room.Update import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.core_db.model.chain.account.MetaAccountPositionUpdate +import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal import io.novafoundation.nova.core_db.model.chain.account.RelationJoinedMetaAccountInfo import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.flow.Flow @@ -77,6 +78,14 @@ private const val META_ACCOUNT_WITH_BALANCE_QUERY = """ @Dao interface MetaAccountDao { + @Transaction + suspend fun insertMetaAccountWithNewPosition(metaAccount: suspend (Int) -> MetaAccountLocal): Long { + val position = nextAccountPosition() + val metaId = insertMetaAccount(metaAccount(position)) + + return metaId + } + @Insert suspend fun insertMetaAccount(metaAccount: MetaAccountLocal): Long @@ -86,6 +95,9 @@ interface MetaAccountDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertChainAccounts(chainAccounts: List) + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertProxies(proxiesLocal: List) + @Query("SELECT * FROM meta_accounts") fun getMetaAccounts(): List @@ -105,6 +117,9 @@ interface MetaAccountDao { @Query(META_ACCOUNT_WITH_BALANCE_QUERY) fun metaAccountWithBalanceFlow(metaId: Long): Flow> + @Query("SELECT * FROM proxy_accounts") + suspend fun getAllProxyAccounts(): List + @Query("UPDATE meta_accounts SET isSelected = (id = :metaId)") suspend fun selectMetaAccount(metaId: Long) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt index 62f98dab38..0a701e53a9 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt @@ -13,10 +13,10 @@ val AddProxyAccount_53_54 = object : Migration(53, 54) { CREATE TABLE IF NOT EXISTS `proxy_accounts` ( `metaId` INTEGER NOT NULL, `chainId` TEXT NOT NULL, - `delegatorAccountId` BLOB NOT NULL, - `rightType` TEXT NOT NULL, + `proxiedAccountId` BLOB NOT NULL, + `proxyType` TEXT NOT NULL, `status` TEXT NOT NULL, - PRIMARY KEY(`metaId`, `delegatorAccountId`, `chainId`, `rightType`), + PRIMARY KEY(`metaId`, `proxiedAccountId`, `chainId`, `proxyType`), FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) """.trimMargin() ) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt index f6a8f7590c..9894682eb6 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt @@ -46,7 +46,7 @@ class MetaAccountLocal( var id: Long = 0 enum class Type { - SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, POLKADOT_VAULT, PROXY + SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, POLKADOT_VAULT, PROXIED } } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt index 5cd5ea2f78..7456adb484 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt @@ -2,8 +2,9 @@ package io.novafoundation.nova.core_db.model.chain.account import androidx.room.Entity import androidx.room.ForeignKey -import androidx.room.Index -import io.novafoundation.nova.core.model.CryptoType +import androidx.room.Ignore +import io.novafoundation.nova.common.utils.Identifiable +import jp.co.soramitsu.fearless_utils.extensions.toHexString @Entity( tableName = "proxy_accounts", @@ -15,21 +16,24 @@ import io.novafoundation.nova.core.model.CryptoType onDelete = ForeignKey.CASCADE ), ], - primaryKeys = ["metaId", "delegatorAccountId", "chainId", "rightType"] + primaryKeys = ["metaId", "proxiedAccountId", "chainId", "proxyType"] ) -class ProxyAccountLocal( +data class ProxyAccountLocal( val metaId: Long, val chainId: String, - val delegatorAccountId: ByteArray, - val rightType: RightType, + val proxiedAccountId: ByteArray, + val proxyType: ProxyType, val status: Status, -) { +) : Identifiable { - enum class RightType { + enum class ProxyType { ANY, UNKNOWN //TODO add more } enum class Status { ACTIVE, DEACTIVATED } + + @Ignore + override val identifier: String = "$metaId:$chainId:${proxiedAccountId.toHexString()}:$proxyType" } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxies.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxies.kt new file mode 100644 index 0000000000..ac6730c9b0 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxies.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.feature_account_api.data.model + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class ProxiedWithProxies( + val accountId: AccountId, + val chainId: ChainId, + val proxies: List +) { + class Proxy( + val accountId: AccountId, + val metaId: Long, + val proxyType: String + ) +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt index 8bc37c793d..ea3154f8db 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -1,8 +1,10 @@ package io.novafoundation.nova.feature_account_api.data.repository -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxies +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId interface ProxyRepository { - suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, chainAccounts: List) + + suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt index cf0eb4b4d9..acdc80a805 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt @@ -47,7 +47,9 @@ class SelectedAccountUseCase( val config = polkadotVaultVariantConfigProvider.variantConfigFor(type.asPolkadotVaultVariantOrThrow()) config.common.iconRes } + LightMetaAccount.Type.LEDGER -> R.drawable.ic_ledger + LightMetaAccount.Type.PROXIED -> null //TODO Add icon for proxy account } SelectedWalletModel( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt index 1b436d8086..f9fefdbab4 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt @@ -31,7 +31,7 @@ interface LightMetaAccount { val type: Type enum class Type { - SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, POLKADOT_VAULT + SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, POLKADOT_VAULT, PROXIED } } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountId.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountId.kt new file mode 100644 index 0000000000..24fbb19f44 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountId.kt @@ -0,0 +1,5 @@ +package io.novafoundation.nova.feature_account_api.domain.model + +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class MetaAccountId(val accountId: AccountId, val metaId: Long) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index 00d807d5de..6f3f76292e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -94,7 +94,7 @@ private fun mapMetaAccountTypeFromLocal(local: MetaAccountLocal.Type): LightMeta MetaAccountLocal.Type.PARITY_SIGNER -> LightMetaAccount.Type.PARITY_SIGNER MetaAccountLocal.Type.LEDGER -> LightMetaAccount.Type.LEDGER MetaAccountLocal.Type.POLKADOT_VAULT -> LightMetaAccount.Type.POLKADOT_VAULT - MetaAccountLocal.Type.PROXY -> TODO() + MetaAccountLocal.Type.PROXIED -> LightMetaAccount.Type.PROXIED } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 3943341bde..102be856c8 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -1,47 +1,165 @@ package io.novafoundation.nova.feature_account_impl.data.proxy +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.common.address.intoKey +import io.novafoundation.nova.common.utils.CollectionDiffer +import io.novafoundation.nova.core_db.dao.MetaAccountDao +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal +import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxies import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity +import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId +import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn +import io.novafoundation.nova.runtime.ext.addressOf +import io.novafoundation.nova.runtime.ext.isSubstrateBased import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import io.novafoundation.nova.runtime.multiNetwork.chainsById +import io.novafoundation.nova.runtime.multiNetwork.findChains +import jp.co.soramitsu.fearless_utils.runtime.AccountId class RealProxySyncService( private val chainRegistry: ChainRegistry, private val proxyRepository: ProxyRepository, - private val accounRepository: AccountRepository + private val accounRepository: AccountRepository, + private val accountDao: MetaAccountDao, + private val identityProvider: IdentityProvider ) : ProxySyncService { override suspend fun startSyncing() { - if (accounRepository.metaAccountFlow()) - val metaAccounts = getMetaAccount() + if (!accounRepository.hasMetaAccounts()) return - val chainIdToChainAcconts = metaAccounts.flatMap { it.chainAccounts.values } - .groupBy { it.chainId } + val metaAccounts = getMetaAccounts() val supportedProxyChains = getSupportedProxyChains() - supportedProxyChains.get("")!!. + val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } - for ((chainId, chainAccounts) in chainIdToChainAcconts) { - proxyRepository.getProxyDelegatorsForAccounts(chainId, chainAccounts) + // proxiedsWithProxies will be usefull when we union differen proxy types to one account + val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> + proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) } + + val newProxies = proxiedsWithProxies.formatToLocalProxies() + val oldProxies = accountDao.getAllProxyAccounts() + + val proxiesDiff = CollectionDiffer.findDiff(newProxies, oldProxies, forceUseNewItems = false) + + insertMetaAndChainAccounts(proxiesDiff) + insertProxies(proxiesDiff) + } + + private suspend fun insertMetaAndChainAccounts(proxiesDiff: CollectionDiffer.Diff) { + val identitiesByChain = proxiesDiff.added.loadProxiedIdentities() + + val chainAccounts = proxiesDiff.added.map { proxy -> + val identity = identitiesByChain[proxy.chainId]?.get(proxy.proxiedAccountId.intoKey()) + val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> + createMetaAccount(proxy.chainId, proxy.proxiedAccountId, identity, nextPosition) + } + createChainAccount(proxiedMetaId, proxy.chainId, proxy.proxiedAccountId) + } + + accountDao.insertChainAccounts(chainAccounts) + } + + private suspend fun insertProxies(proxiesDiff: CollectionDiffer.Diff) { + val deactivatedProxies = proxiesDiff.removed.map { it.copy(status = ProxyAccountLocal.Status.DEACTIVATED) } + + accountDao.insertProxies(proxiesDiff.newOrUpdated) + accountDao.insertProxies(deactivatedProxies) } override suspend fun syncForMetaAccount(metaAccount: MetaAccount) { TODO("provide updater to sync proxy delegators for new added accounts") } - private suspend fun getMetaAccount(): List { + private suspend fun getMetaAccounts(): List { return accounRepository.allMetaAccounts() - .filter { it.type != LightMetaAccount.Type.WATCH_ONLY || true/*it.type != LightMetaAccount.Type.PROXY*/ } + .filter { + when (it.type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.LEDGER, + LightMetaAccount.Type.POLKADOT_VAULT -> true + + LightMetaAccount.Type.WATCH_ONLY -> true // TODO true need for test. Change to false + LightMetaAccount.Type.PROXIED -> false + } + } + } + + private suspend fun getSupportedProxyChains(): List { + return chainRegistry.findChains { it.supportProxy } + } + + private suspend fun Chain.getAvailableAccountIds(metaAccounts: List): List { + return metaAccounts.mapNotNull { metaAccount -> + val accountId = metaAccount.accountIdIn(chain = this) + accountId?.let { + MetaAccountId(accountId, metaAccount.id) + } + } + } + + private suspend fun createMetaAccount(chainId: ChainId, proxiedAccountId: AccountId, identity: Identity?, position: Int): MetaAccountLocal { + val chain = chainRegistry.getChain(chainId) + return MetaAccountLocal( + substratePublicKey = null, + substrateCryptoType = null, + substrateAccountId = if (chain.isSubstrateBased) proxiedAccountId else null, + ethereumPublicKey = null, + ethereumAddress = if (chain.isEthereumBased) proxiedAccountId else null, + name = identity?.name ?: chain.addressOf(proxiedAccountId), + isSelected = false, + position = position, + type = MetaAccountLocal.Type.PROXIED, + ) + } + + private suspend fun createChainAccount(metaId: Long, chainId: ChainId, accountId: AccountId): ChainAccountLocal { + return ChainAccountLocal( + metaId = metaId, + chainId = chainId, + publicKey = null, + accountId = accountId, + cryptoType = null + ) + } + + private fun createProxyAccount( + metaId: Long, + chainId: ChainId, + proxiedAccountId: AccountId + ): ProxyAccountLocal { + return ProxyAccountLocal( + metaId = metaId, + chainId = chainId, + proxiedAccountId = proxiedAccountId, + proxyType = ProxyAccountLocal.ProxyType.ANY, //TODO map proxyType + status = ProxyAccountLocal.Status.ACTIVE + ) + } + + private fun List.formatToLocalProxies(): List { + return flatMap { proxied -> + proxied.proxies.map { proxy -> + createProxyAccount(proxy.metaId, proxied.chainId, proxied.accountId) + } + } } - private suspend fun getSupportedProxyChains(): Map { - return chainRegistry.chainsById() - .filterValues { it.supportProxy } + private suspend fun List.loadProxiedIdentities(): Map> { + return this.groupBy { it.chainId } + .mapValues { (chainId, proxiesWithProxieds) -> + val proxiedAccountIds = proxiesWithProxieds.map { it.proxiedAccountId } + identityProvider.identitiesFor(proxiedAccountIds, chainId) + } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index f376650f63..5e4dc5a46f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -1,13 +1,15 @@ package io.novafoundation.nova.feature_account_impl.data.repository +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.common.address.intoKey +import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum import io.novafoundation.nova.common.data.network.runtime.binding.castToList import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct import io.novafoundation.nova.common.data.network.runtime.binding.getTyped import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxies import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount.ProxyAccount -import io.novafoundation.nova.runtime.call.RuntimeCallsApi +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource import jp.co.soramitsu.fearless_utils.runtime.AccountId @@ -18,22 +20,73 @@ class RealProxyRepository( private val remoteSource: StorageDataSource ) : ProxyRepository { - override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, chainAccounts: List) { - val allProxiesOnChain = remoteSource.query(chainId) { + override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List { + val delegatorToProxies = receiveAllProxies(chainId) + + val accountIdToMetaAccounts = metaAccountIds.associateBy { it.accountId.intoKey() } + + return delegatorToProxies + .mapNotNull { (delegator, proxies) -> + val matchedProxies = matchProxiesToAccountAccountsAndMap(proxies, accountIdToMetaAccounts) + + if (matchedProxies.isEmpty()) return@mapNotNull null + + delegator to matchedProxies + }.map { (delegator, proxies) -> + mapToProxiedWithProxies(chainId, delegator, proxies) + } + } + + private suspend fun receiveAllProxies(chainId: ChainId): Map> { + return remoteSource.query(chainId) { runtime.metadata.module(Modules.PROXY) .storage("Proxies") - .entries { result, storageKeyComponent -> - storageKeyComponent.values - bindProxyAccounts(result) - } + .entries( + keyExtractor = { (accountId: AccountId) -> AccountIdKey(accountId) }, + binding = { result, _ -> + bindProxyAccounts(result) + }, + onDecodeException = { } + ) } } - private fun bindProxyAccounts(dynamicInstance: Any?): List> { - val delegates = dynamicInstance.castToList() - val proxies = delegates[0].castToStruct() - val proxyAccountId: ByteArray = proxies.getTyped("delegate") - val proxyType: String = proxies.getTyped("proxyType") - return listOf() + private fun bindProxyAccounts(dynamicInstance: Any?): Map { + val root = dynamicInstance.castToList() + val proxies = root[0].castToList() + + return proxies.map { + val proxy = it.castToStruct() + val proxyAccountId: ByteArray = proxy.getTyped("delegate") + val proxyType = proxy.get("proxyType").castToDictEnum() + proxyAccountId.intoKey() to proxyType.name + }.toMap() + } + + private fun mapToProxiedWithProxies( + chainId: ChainId, + delegator: AccountIdKey, + proxies: List + ): ProxiedWithProxies { + return ProxiedWithProxies( + accountId = delegator.value, + chainId = chainId, + proxies = proxies + ) + } + + private fun matchProxiesToAccountAccountsAndMap( + proxies: Map, + accountIdToMetaAccounts: Map + ): List { + return proxies.mapNotNull { (proxyAccountId, proxyType) -> + val matchedAccount = accountIdToMetaAccounts[proxyAccountId] ?: return@mapNotNull null + + ProxiedWithProxies.Proxy( + accountId = proxyAccountId.value, + metaId = matchedAccount.metaId, + proxyType = proxyType + ) + } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt index f2a990b7cc..71334469e8 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt @@ -27,6 +27,7 @@ internal class RealSignerProvider( LightMetaAccount.Type.PARITY_SIGNER -> paritySignerSigner LightMetaAccount.Type.POLKADOT_VAULT -> polkadotVaultSigner LightMetaAccount.Type.LEDGER -> ledgerSigner + LightMetaAccount.Type.PROXIED -> TODO() } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index b5c78aa2d3..277b6fa0dd 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -71,6 +71,8 @@ import io.novafoundation.nova.feature_account_impl.domain.NodeHostValidator import io.novafoundation.nova.feature_account_impl.domain.account.add.AddAccountInteractor import io.novafoundation.nova.feature_account_impl.domain.account.advancedEncryption.AdvancedEncryptionInteractor import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults +import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider +import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountDetailsInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountTypePresentationMapper @@ -113,11 +115,15 @@ class AccountFeatureModule { fun provideProxySyncService( chainRegistry: ChainRegistry, proxyRepository: ProxyRepository, - accounRepository: AccountRepository + accounRepository: AccountRepository, + metaAccountDao: MetaAccountDao, + @OnChainIdentity identityProvider: IdentityProvider ): ProxySyncService = RealProxySyncService( chainRegistry, proxyRepository, - accounRepository + accounRepository, + metaAccountDao, + identityProvider ) @Provides diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt index 869365bfcd..408fa0d86a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt @@ -103,7 +103,8 @@ class MetaAccountGroupingInteractorImpl( LightMetaAccount.Type.POLKADOT_VAULT -> 1 LightMetaAccount.Type.PARITY_SIGNER -> 2 LightMetaAccount.Type.LEDGER -> 3 - LightMetaAccount.Type.WATCH_ONLY -> 4 + LightMetaAccount.Type.PROXIED -> 4 + LightMetaAccount.Type.WATCH_ONLY -> 5 } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt index 4e1b4436f5..f7b70088e5 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt @@ -31,6 +31,8 @@ class MetaAccountTypePresentationMapper( iconRes = R.drawable.ic_ledger, title = resourceManager.getString(R.string.common_ledger) ) + + LightMetaAccount.Type.PROXIED -> null //TODO Add icon for proxy account } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsViewModel.kt index 51a098557c..d759a6d6e9 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsViewModel.kt @@ -106,7 +106,7 @@ class AccountDetailsViewModel( private suspend fun mapFromToTextHeader(from: AccountInChain.From): TextHeader? { return when (metaAccount().type) { - Type.LEDGER, Type.PARITY_SIGNER, Type.POLKADOT_VAULT -> null + Type.LEDGER, Type.PARITY_SIGNER, Type.POLKADOT_VAULT, Type.PROXIED -> null Type.SECRETS, Type.WATCH_ONLY -> { val resId = when (from) { AccountInChain.From.META_ACCOUNT -> R.string.account_shared_secret @@ -125,6 +125,7 @@ class AccountDetailsViewModel( val polkadotVaultVariant = metaAccount.type.asPolkadotVaultVariantOrThrow() resourceManager.formatWithPolkadotVaultLabel(R.string.account_details_parity_signer_not_supported, polkadotVaultVariant) } + else -> resourceManager.getString(R.string.account_no_chain_projection) } @@ -177,7 +178,7 @@ class AccountDetailsViewModel( return when (accountType) { Type.SECRETS -> setOf(AccountAction.EXPORT, AccountAction.CHANGE) Type.WATCH_ONLY -> setOf(AccountAction.CHANGE) - Type.PARITY_SIGNER, Type.POLKADOT_VAULT -> emptySet() + Type.PARITY_SIGNER, Type.POLKADOT_VAULT, Type.PROXIED -> emptySet() Type.LEDGER -> setOf(AccountAction.CHANGE) } } @@ -191,6 +192,7 @@ class AccountDetailsViewModel( ), text = resourceManager.getString(R.string.account_details_watch_only_alert) ) + Type.PARITY_SIGNER, Type.POLKADOT_VAULT -> { val polkadotVaultVariant = accountType.asPolkadotVaultVariantOrThrow() val variantConfig = polkadotVaultVariantConfigProvider.variantConfigFor(polkadotVaultVariant) @@ -203,6 +205,7 @@ class AccountDetailsViewModel( text = resourceManager.formatWithPolkadotVaultLabel(R.string.account_details_parity_signer_alert, polkadotVaultVariant) ) } + Type.SECRETS -> null Type.LEDGER -> AccountTypeAlert( style = AlertView.Style( @@ -211,6 +214,8 @@ class AccountDetailsViewModel( ), text = resourceManager.getString(R.string.ledger_wallet_details_description) ) + + Type.PROXIED -> TODO() // Make a valid alert here } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/mixin/addAccountChooser/AddAccountLauncherProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/mixin/addAccountChooser/AddAccountLauncherProvider.kt index 846ea9e0e7..e2843f8a4e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/mixin/addAccountChooser/AddAccountLauncherProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/mixin/addAccountChooser/AddAccountLauncherProvider.kt @@ -45,7 +45,7 @@ class AddAccountLauncherProvider( LightMetaAccount.Type.SECRETS -> launchAddFromSecrets(chain, metaAccount) LightMetaAccount.Type.WATCH_ONLY -> launchAddWatchOnly(chain, metaAccount) // adding chain accounts is not supported for Polkadot Vault like wallets - LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.POLKADOT_VAULT -> {} + LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.POLKADOT_VAULT, LightMetaAccount.Type.PROXIED -> {} LightMetaAccount.Type.LEDGER -> launchAddLedger(chain, metaAccount) } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt index 16bb84c680..734e02a9bd 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt @@ -6,6 +6,7 @@ import io.novafoundation.nova.common.data.network.runtime.binding.fromByteArrayO import io.novafoundation.nova.common.data.network.runtime.binding.fromHexOrIncompatible import io.novafoundation.nova.common.data.network.runtime.binding.incompatible import io.novafoundation.nova.common.utils.ComponentHolder +import io.novafoundation.nova.common.utils.mapValuesNotNull import io.novafoundation.nova.common.utils.splitKeyToComponents import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder @@ -60,7 +61,8 @@ abstract class BaseStorageQueryContext( override suspend fun StorageEntry.entries( vararg prefixArgs: Any?, keyExtractor: (StorageKeyComponents) -> K, - binding: DynamicInstanceBinderWithKey + binding: DynamicInstanceBinderWithKey, + onDecodeException: (Exception) -> Unit ): Map { val prefix = storageKey(runtime, *prefixArgs) @@ -70,14 +72,16 @@ abstract class BaseStorageQueryContext( entries = entries, storageEntry = this, keyExtractor = keyExtractor, - binding = binding + binding = binding, + onDecodeException = onDecodeException ) } override suspend fun StorageEntry.entries( keysArguments: List>, keyExtractor: (StorageKeyComponents) -> K, - binding: DynamicInstanceBinderWithKey + binding: DynamicInstanceBinderWithKey, + onDecodeException: (Exception) -> Unit ): Map { val entries = queryKeys(storageKeys(runtime, keysArguments), at) @@ -85,7 +89,8 @@ abstract class BaseStorageQueryContext( entries = entries, storageEntry = this, keyExtractor = keyExtractor, - binding = binding + binding = binding, + onDecodeException = onDecodeException ) } @@ -237,6 +242,7 @@ abstract class BaseStorageQueryContext( storageEntry: StorageEntry, keyExtractor: (StorageKeyComponents) -> K, binding: DynamicInstanceBinderWithKey, + onDecodeException: (Exception) -> Unit = { throw it } ): Map { val returnType = storageEntry.type.value ?: incompatible() @@ -244,10 +250,14 @@ abstract class BaseStorageQueryContext( val keyComponents = ComponentHolder(storageEntry.splitKey(runtime, key)) keyExtractor(keyComponents) - }.mapValues { (key, value) -> - val decoded = value?.let { returnType.fromHexOrIncompatible(value, runtime) } - - binding(decoded, key) + }.mapValuesNotNull { (key, value) -> + try { + val decoded = value?.let { returnType.fromHexOrIncompatible(value, runtime) } + binding(decoded, key) + } catch (e: Exception) { + onDecodeException(e) + null + } } } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt index 7c5bdaf8ca..8572d1a8ec 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt @@ -49,7 +49,8 @@ interface StorageQueryContext { suspend fun StorageEntry.entries( vararg prefixArgs: Any?, keyExtractor: (StorageKeyComponents) -> K, - binding: DynamicInstanceBinderWithKey + binding: DynamicInstanceBinderWithKey, + onDecodeException: (Exception) -> Unit = { throw it } ): Map suspend fun StorageEntry.entriesRaw( @@ -63,7 +64,8 @@ interface StorageQueryContext { suspend fun StorageEntry.entries( keysArguments: List>, keyExtractor: (StorageKeyComponents) -> K, - binding: DynamicInstanceBinderWithKey + binding: DynamicInstanceBinderWithKey, + onDecodeException: (Exception) -> Unit = { throw it } ): Map suspend fun StorageEntry.query( From de95e555dc0975c316fe5c78300a3b7d152d76d4 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 30 Nov 2023 11:30:50 +0100 Subject: [PATCH 006/100] Run ktlint --- .../nova/app/root/domain/RootInteractor.kt | 1 - .../app/root/presentation/RootViewModel.kt | 1 - .../migrations/53_54_AddProxyAccount.kt | 2 -- .../model/chain/account/ProxyAccountLocal.kt | 2 +- .../data/repository/ProxyRepository.kt | 2 +- .../interfaces/SelectedAccountUseCase.kt | 2 +- .../data/mappers/Mappers.kt | 18 +++++++++--------- .../data/proxy/RealProxySyncService.kt | 2 +- .../MetaAccountTypePresentationMapper.kt | 2 +- 9 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt b/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt index 2348022cf8..bb297b6d01 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt @@ -3,7 +3,6 @@ package io.novafoundation.nova.app.root.domain import io.novafoundation.nova.core.updater.Updater import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_impl.data.proxy.RealProxySyncService import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem import io.novafoundation.nova.feature_buy_impl.domain.providers.ExternalProvider import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt index e394a94894..3cc78b52f5 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt @@ -16,7 +16,6 @@ import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.core.updater.Updater -import io.novafoundation.nova.feature_account_impl.data.proxy.RealProxySyncService import io.novafoundation.nova.feature_crowdloan_api.domain.contributions.ContributionsInteractor import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt index 0a701e53a9..bc2645882b 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt @@ -2,8 +2,6 @@ package io.novafoundation.nova.core_db.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import io.novafoundation.nova.core_db.converters.AssetConverters -import io.novafoundation.nova.core_db.model.AssetLocal val AddProxyAccount_53_54 = object : Migration(53, 54) { diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt index 7456adb484..6941653775 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt @@ -27,7 +27,7 @@ data class ProxyAccountLocal( ) : Identifiable { enum class ProxyType { - ANY, UNKNOWN //TODO add more + ANY, UNKNOWN // TODO add more } enum class Status { diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt index ea3154f8db..8288b6d95b 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -5,6 +5,6 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId interface ProxyRepository { - + suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt index acdc80a805..0c6195d0c3 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt @@ -49,7 +49,7 @@ class SelectedAccountUseCase( } LightMetaAccount.Type.LEDGER -> R.drawable.ic_ledger - LightMetaAccount.Type.PROXIED -> null //TODO Add icon for proxy account + LightMetaAccount.Type.PROXIED -> null // TODO Add icon for proxy account } SelectedWalletModel( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index 6f3f76292e..816d929902 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -38,21 +38,21 @@ fun mapCryptoTypeToCryptoTypeModel( ): CryptoTypeModel { val name = when (encryptionType) { CryptoType.SR25519 -> "${resourceManager.getString(R.string.sr25519_selection_title)} ${ - resourceManager.getString( - R.string.sr25519_selection_subtitle - ) + resourceManager.getString( + R.string.sr25519_selection_subtitle + ) }" CryptoType.ED25519 -> "${resourceManager.getString(R.string.ed25519_selection_title)} ${ - resourceManager.getString( - R.string.ed25519_selection_subtitle - ) + resourceManager.getString( + R.string.ed25519_selection_subtitle + ) }" CryptoType.ECDSA -> "${resourceManager.getString(R.string.ecdsa_selection_title)} ${ - resourceManager.getString( - R.string.ecdsa_selection_subtitle - ) + resourceManager.getString( + R.string.ecdsa_selection_subtitle + ) }" } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 102be856c8..b9cbe912e0 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -142,7 +142,7 @@ class RealProxySyncService( metaId = metaId, chainId = chainId, proxiedAccountId = proxiedAccountId, - proxyType = ProxyAccountLocal.ProxyType.ANY, //TODO map proxyType + proxyType = ProxyAccountLocal.ProxyType.ANY, // TODO map proxyType status = ProxyAccountLocal.Status.ACTIVE ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt index f7b70088e5..6368e0456c 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt @@ -32,7 +32,7 @@ class MetaAccountTypePresentationMapper( title = resourceManager.getString(R.string.common_ledger) ) - LightMetaAccount.Type.PROXIED -> null //TODO Add icon for proxy account + LightMetaAccount.Type.PROXIED -> null // TODO Add icon for proxy account } } } From 8b00fb8787d86bc4a370ae421fe80f697185b83b Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 30 Nov 2023 11:47:10 +0100 Subject: [PATCH 007/100] Update RealProxySyncService.kt --- .../feature_account_impl/data/proxy/RealProxySyncService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index b9cbe912e0..12f80855d9 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -89,7 +89,7 @@ class RealProxySyncService( LightMetaAccount.Type.LEDGER, LightMetaAccount.Type.POLKADOT_VAULT -> true - LightMetaAccount.Type.WATCH_ONLY -> true // TODO true need for test. Change to false + LightMetaAccount.Type.WATCH_ONLY -> false LightMetaAccount.Type.PROXIED -> false } } From a0ed3e6033d6c61361347f123efd651c7a389c5c Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 30 Nov 2023 11:47:47 +0100 Subject: [PATCH 008/100] Minor changes --- .../feature_account_impl/data/proxy/RealProxySyncService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 12f80855d9..1ddf5f897a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -89,7 +89,7 @@ class RealProxySyncService( LightMetaAccount.Type.LEDGER, LightMetaAccount.Type.POLKADOT_VAULT -> true - LightMetaAccount.Type.WATCH_ONLY -> false + LightMetaAccount.Type.WATCH_ONLY, LightMetaAccount.Type.PROXIED -> false } } From 33a939429158c39aa98e79b1626c694f0ce1f617 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 30 Nov 2023 11:50:43 +0100 Subject: [PATCH 009/100] Minor changes --- .../data/repository/RealProxyRepository.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index 5e4dc5a46f..2f8a002cc3 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -27,7 +27,7 @@ class RealProxyRepository( return delegatorToProxies .mapNotNull { (delegator, proxies) -> - val matchedProxies = matchProxiesToAccountAccountsAndMap(proxies, accountIdToMetaAccounts) + val matchedProxies = matchProxiesToAccountsAndMap(proxies, accountIdToMetaAccounts) if (matchedProxies.isEmpty()) return@mapNotNull null @@ -75,7 +75,7 @@ class RealProxyRepository( ) } - private fun matchProxiesToAccountAccountsAndMap( + private fun matchProxiesToAccountsAndMap( proxies: Map, accountIdToMetaAccounts: Map ): List { From 1ac44c31423ce01d07f507c9e120556ebfe83372 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 1 Dec 2023 09:48:20 +0100 Subject: [PATCH 010/100] Clean code --- .../converters/ProxyAccountConverters.kt | 10 ------- .../model/chain/account/ProxyAccountLocal.kt | 6 +--- .../domain/model/MetaAccount.kt | 11 ------- .../domain/model/ProxyAccount.kt | 29 +++++++++++++++++++ .../data/proxy/RealProxySyncService.kt | 7 +++-- 5 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt index 846fbe3089..85e078ccf6 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt @@ -4,16 +4,6 @@ import androidx.room.TypeConverter import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal class ProxyAccountConverters { - @TypeConverter - fun fromRightType(type: ProxyAccountLocal.ProxyType): String { - return type.name - } - - @TypeConverter - fun toRightType(name: String): ProxyAccountLocal.ProxyType { - return ProxyAccountLocal.ProxyType.valueOf(name) - } - @TypeConverter fun fromStatusType(type: ProxyAccountLocal.Status): String { return type.name diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt index 6941653775..73986bb4a0 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt @@ -22,14 +22,10 @@ data class ProxyAccountLocal( val metaId: Long, val chainId: String, val proxiedAccountId: ByteArray, - val proxyType: ProxyType, + val proxyType: String, val status: Status, ) : Identifiable { - enum class ProxyType { - ANY, UNKNOWN // TODO add more - } - enum class Status { ACTIVE, DEACTIVATED } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt index f9fefdbab4..94e908f87e 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt @@ -78,17 +78,6 @@ class MetaAccount( val accountId: ByteArray, val cryptoType: CryptoType?, ) - - class ProxyAccount( - val metaId: Long, - val proxyAccountId: ByteArray, - val proxyType: ProxyType, - ) { - - enum class ProxyType { - ANY, UNSUPPORTED - } - } } fun MetaAccount.hasAccountIn(chain: Chain) = when { diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt new file mode 100644 index 0000000000..f39582168b --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt @@ -0,0 +1,29 @@ +package io.novafoundation.nova.feature_account_api.domain.model + +class ProxyAccount( + val metaId: Long, + val proxyAccountId: ByteArray, + val proxyType: ProxyType, +) { + + sealed interface ProxyType { + + object Any : ProxyType + + object NonTransfer : ProxyType + + object Governance : ProxyType + + object Staking : ProxyType + + object IdentityJudgement : ProxyType + + object CancelProxy : ProxyType + + object Auction : ProxyType + + object NominationPools : ProxyType + + class Other(val name: String) : ProxyType + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 1ddf5f897a..4997369e93 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -136,13 +136,14 @@ class RealProxySyncService( private fun createProxyAccount( metaId: Long, chainId: ChainId, - proxiedAccountId: AccountId + proxiedAccountId: AccountId, + proxyType: String ): ProxyAccountLocal { return ProxyAccountLocal( metaId = metaId, chainId = chainId, proxiedAccountId = proxiedAccountId, - proxyType = ProxyAccountLocal.ProxyType.ANY, // TODO map proxyType + proxyType = proxyType, status = ProxyAccountLocal.Status.ACTIVE ) } @@ -150,7 +151,7 @@ class RealProxySyncService( private fun List.formatToLocalProxies(): List { return flatMap { proxied -> proxied.proxies.map { proxy -> - createProxyAccount(proxy.metaId, proxied.chainId, proxied.accountId) + createProxyAccount(proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) } } } From b00a4a525bcb2bc442d40ce2d54e3df09f44ee51 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 5 Dec 2023 01:20:28 +0100 Subject: [PATCH 011/100] Select wallet screen + logic --- .../common/address/AddressIconGenerator.kt | 4 +- .../nova/common/utils/SpannableExt.kt | 25 ++- .../src/main/res/drawable/bg_chain_wallet.xml | 8 + common/src/main/res/drawable/ic_proxy.xml | 10 + common/src/main/res/values/strings.xml | 12 ++ .../converters/ProxyAccountConverters.kt | 7 +- .../nova/core_db/dao/MetaAccountDao.kt | 6 + .../migrations/53_54_AddProxyAccount.kt | 15 +- .../chain/account/JoinedMetaAccountInfo.kt | 5 + .../model/chain/account/MetaAccountLocal.kt | 20 +- .../model/chain/account/ProxyAccountLocal.kt | 34 +++- ...xiedWithProxies.kt => ProxiedWithProxy.kt} | 12 +- .../data/repository/ProxyRepository.kt | 4 +- .../domain/interfaces/AccountRepository.kt | 2 + .../interfaces/SelectedAccountUseCase.kt | 2 +- .../domain/model/MetaAccount.kt | 2 +- .../domain/model/MetaAccountAssetBalance.kt | 3 + .../domain/model/ProxyAccount.kt | 5 +- .../account/listing/AccountListAdapter.kt | 12 +- .../account/listing/AccountUi.kt | 3 +- .../account/wallet/WalletUiUseCase.kt | 3 +- .../src/main/res/layout/item_account.xml | 16 +- .../data/mappers/Mappers.kt | 45 ++++- .../data/proxy/RealProxySyncService.kt | 118 ++++++++---- .../data/proxy/RealProxySyncService3.kt | 182 ++++++++++++++++++ .../data/repository/AccountRepositoryImpl.kt | 4 + .../data/repository/ParitySignerRepository.kt | 4 +- .../data/repository/RealProxyRepository.kt | 26 +-- .../data/repository/WatchOnlyRepository.kt | 4 +- .../datasource/AccountDataSource.kt | 2 + .../datasource/AccountDataSourceImpl.kt | 11 +- .../di/AccountFeatureModule.kt | 3 +- .../MetaAccountGroupingInteractorImpl.kt | 27 ++- .../MetaAccountTypePresentationMapper.kt | 5 +- ...aAccountValidForTransactionListingMixin.kt | 1 + .../MetaAccountWithBalanceListingMixin.kt | 57 +++++- .../account/common/listing/ProxyTypeMapper.kt | 21 ++ .../account/list/WalletListFragment.kt | 7 +- .../management/WalletManagmentFragment.kt | 7 +- .../account/wallet/WalletUiUseCaseImpl.kt | 14 +- .../data/repository/LedgerRepository.kt | 4 +- .../SelectAddressLedgerFragment.kt | 6 +- .../SelectAddressLedgerViewModel.kt | 1 + 43 files changed, 630 insertions(+), 129 deletions(-) create mode 100644 common/src/main/res/drawable/bg_chain_wallet.xml create mode 100644 common/src/main/res/drawable/ic_proxy.xml rename feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/{ProxiedWithProxies.kt => ProxiedWithProxy.kt} (65%) create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService3.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyTypeMapper.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/address/AddressIconGenerator.kt b/common/src/main/java/io/novafoundation/nova/common/address/AddressIconGenerator.kt index 20e9e32a71..a2ec6e5c58 100644 --- a/common/src/main/java/io/novafoundation/nova/common/address/AddressIconGenerator.kt +++ b/common/src/main/java/io/novafoundation/nova/common/address/AddressIconGenerator.kt @@ -90,6 +90,8 @@ class StatelessAddressIconGenerator( val sizeInPx = resourceManager.measureInPx(sizeInDp) val backgroundColor = resourceManager.getColor(backgroundColorRes) - iconGenerator.getSvgImage(accountId, sizeInPx, backgroundColor = backgroundColor) + val drawable = iconGenerator.getSvgImage(accountId, sizeInPx, backgroundColor = backgroundColor) + drawable.setBounds(0, 0, sizeInPx, sizeInPx) + drawable } } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt index c26f57fbab..bd45df61ae 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt @@ -28,12 +28,29 @@ fun Spannable.setFullSpan(span: Any): Spannable { // This method is nice for ImageSpan fun Spannable.setEndSpan(span: Any): Spannable { - val spannable = SpannableStringBuilder(this) - spannable.append(" ") - .setSpan(span, length, length + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - return spannable + return SpannableStringBuilder(this) + .appendEnd(span) } +fun SpannableStringBuilder.appendSpace(): SpannableStringBuilder { + append(" ") + return this +} + +fun SpannableStringBuilder.append(text: CharSequence?, span: Any): SpannableStringBuilder { + val startSpan = length + append(text) + .setSpan(span, startSpan, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return this +} + +fun SpannableStringBuilder.appendEnd(span: Any): SpannableStringBuilder { + appendSpace() + .setSpan(span, length - 1, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + return this +} + + fun clickableSpan(onClick: () -> Unit) = object : ClickableSpan() { override fun updateDrawState(ds: TextPaint) { ds.isUnderlineText = false diff --git a/common/src/main/res/drawable/bg_chain_wallet.xml b/common/src/main/res/drawable/bg_chain_wallet.xml new file mode 100644 index 0000000000..42d8f9feb1 --- /dev/null +++ b/common/src/main/res/drawable/bg_chain_wallet.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/common/src/main/res/drawable/ic_proxy.xml b/common/src/main/res/drawable/ic_proxy.xml new file mode 100644 index 0000000000..078d22f110 --- /dev/null +++ b/common/src/main/res/drawable/ic_proxy.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index a5d6d8b1f6..721a6ebe82 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,6 +1,18 @@ + %s proxy: + Any + Non Transfer + Governance + Staking + Identity Judgement + Cancel Proxy + Auction + Nomination Pools + + Delegated to you (Proxieds) + Chain is not found Governance type is not specified Governance type is not supported diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt index 85e078ccf6..6239abdda6 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt @@ -1,16 +1,17 @@ package io.novafoundation.nova.core_db.converters import androidx.room.TypeConverter +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal class ProxyAccountConverters { @TypeConverter - fun fromStatusType(type: ProxyAccountLocal.Status): String { + fun fromStatusType(type: MetaAccountLocal.Status): String { return type.name } @TypeConverter - fun toStatusType(name: String): ProxyAccountLocal.Status { - return ProxyAccountLocal.Status.valueOf(name) + fun toStatusType(name: String): MetaAccountLocal.Status { + return MetaAccountLocal.Status.valueOf(name) } } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 4f6c6f6e16..1d8518cbd9 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -111,6 +111,9 @@ interface MetaAccountDao { @Query("SELECT * FROM meta_accounts") fun getJoinedMetaAccountsInfoFlow(): Flow> + @Query("SELECT * FROM meta_accounts WHERE status = :status") + fun getJoinedMetaAccountsInfoByStatusFlow(status: MetaAccountLocal.Status): Flow> + @Query(META_ACCOUNTS_WITH_BALANCE_QUERY) fun metaAccountsWithBalanceFlow(): Flow> @@ -174,6 +177,9 @@ interface MetaAccountDao { @Query("SELECT EXISTS(SELECT * FROM meta_accounts)") suspend fun hasMetaAccounts(): Boolean + + @Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)") + suspend fun changeAccountsStatus(metaIds: List, status: MetaAccountLocal.Status) } class MetaAccountWithBalanceLocal( diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt index bc2645882b..89a2f2ecb1 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.core_db.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal val AddProxyAccount_53_54 = object : Migration(53, 54) { @@ -9,16 +10,24 @@ val AddProxyAccount_53_54 = object : Migration(53, 54) { database.execSQL( """ CREATE TABLE IF NOT EXISTS `proxy_accounts` ( - `metaId` INTEGER NOT NULL, + `proxiedMetaId` INTEGER NOT NULL, + `proxyMetaId` INTEGER NOT NULL, `chainId` TEXT NOT NULL, `proxiedAccountId` BLOB NOT NULL, `proxyType` TEXT NOT NULL, - `status` TEXT NOT NULL, PRIMARY KEY(`metaId`, `proxiedAccountId`, `chainId`, `proxyType`), - FOREIGN KEY(`metaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) + FOREIGN KEY(`proxiedMetaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, + FOREIGN KEY(`proxyMetaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) """.trimMargin() ) + database.execSQL( + """ + ALTER TABLE 'meta_accounts' ADD COLUMN `status` TEXT NOT NULL DEFAULT ${MetaAccountLocal.Status.ACTIVE}, + ADD COLUMN `parentMetaId` INTEGER, + FOREIGN KEY(`parentMetaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE + """.trimMargin() + ) database.execSQL("ALTER TABLE 'chains' ADD COLUMN `supportProxy` INTEGER NOT NULL DEFAULT 0") } } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/JoinedMetaAccountInfo.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/JoinedMetaAccountInfo.kt index 2504b7a1f7..84ebe2286a 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/JoinedMetaAccountInfo.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/JoinedMetaAccountInfo.kt @@ -8,6 +8,8 @@ interface JoinedMetaAccountInfo { val metaAccount: MetaAccountLocal val chainAccounts: List + + val proxyAccountLocal: ProxyAccountLocal? } class RelationJoinedMetaAccountInfo( @@ -16,4 +18,7 @@ class RelationJoinedMetaAccountInfo( @Relation(parentColumn = "id", entityColumn = "metaId", entity = ChainAccountLocal::class) override val chainAccounts: List, + + @Relation(parentColumn = "id", entityColumn = "proxiedMetaId", entity = ProxyAccountLocal::class) + override val proxyAccountLocal: ProxyAccountLocal?, ) : JoinedMetaAccountInfo diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt index 9894682eb6..e469ea8e6d 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt @@ -1,15 +1,26 @@ package io.novafoundation.nova.core_db.model.chain.account +import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.ForeignKey import androidx.room.Index import androidx.room.PrimaryKey import io.novafoundation.nova.core.model.CryptoType @Entity( tableName = MetaAccountLocal.TABLE_NAME, + foreignKeys = [ + ForeignKey( + parentColumns = ["id"], + childColumns = ["parentMetaId"], + entity = MetaAccountLocal::class, + onDelete = ForeignKey.CASCADE + ) + ], indices = [ Index(value = ["substrateAccountId"]), - Index(value = ["ethereumAddress"]) + Index(value = ["ethereumAddress"]), + Index(value = ["parentMetaId"]) ] ) class MetaAccountLocal( @@ -19,11 +30,18 @@ class MetaAccountLocal( val ethereumPublicKey: ByteArray?, val ethereumAddress: ByteArray?, val name: String, + val parentMetaId: Long?, val isSelected: Boolean, val position: Int, val type: Type, + @ColumnInfo(defaultValue = "ACTIVE") + val status: Status, ) { + enum class Status { + ACTIVE, DEACTIVATED + } + companion object Table { const val TABLE_NAME = "meta_accounts" diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt index 73986bb4a0..78dad3ab95 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.core_db.model.chain.account import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Ignore +import androidx.room.Index import io.novafoundation.nova.common.utils.Identifiable import jp.co.soramitsu.fearless_utils.extensions.toHexString @@ -11,25 +12,38 @@ import jp.co.soramitsu.fearless_utils.extensions.toHexString foreignKeys = [ ForeignKey( parentColumns = ["id"], - childColumns = ["metaId"], + childColumns = ["proxiedMetaId"], + entity = MetaAccountLocal::class, + onDelete = ForeignKey.CASCADE + ), + ForeignKey( + parentColumns = ["id"], + childColumns = ["proxyMetaId"], entity = MetaAccountLocal::class, onDelete = ForeignKey.CASCADE ), ], - primaryKeys = ["metaId", "proxiedAccountId", "chainId", "proxyType"] + primaryKeys = ["proxyMetaId", "proxiedAccountId", "chainId", "proxyType"] ) data class ProxyAccountLocal( - val metaId: Long, + val proxiedMetaId: Long, + val proxyMetaId: Long, val chainId: String, val proxiedAccountId: ByteArray, - val proxyType: String, - val status: Status, + val proxyType: String ) : Identifiable { - enum class Status { - ACTIVE, DEACTIVATED - } - @Ignore - override val identifier: String = "$metaId:$chainId:${proxiedAccountId.toHexString()}:$proxyType" + override val identifier: String = makeIdentifier(proxyMetaId, chainId, proxiedAccountId, proxyType) + + companion object { + fun makeIdentifier( + proxyMetaId: Long, + chainId: String, + proxiedAccountId: ByteArray, + proxyType: String + ): String { + return "$proxyMetaId:$chainId:${proxiedAccountId.toHexString()}:$proxyType" + } + } } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxies.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxy.kt similarity index 65% rename from feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxies.kt rename to feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxy.kt index ac6730c9b0..887e97763c 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxies.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxy.kt @@ -3,11 +3,15 @@ package io.novafoundation.nova.feature_account_api.data.model import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.runtime.AccountId -class ProxiedWithProxies( - val accountId: AccountId, - val chainId: ChainId, - val proxies: List +class ProxiedWithProxy( + val proxied: Proxied, + val proxy: Proxy ) { + class Proxied( + val accountId: AccountId, + val chainId: ChainId + ) + class Proxy( val accountId: AccountId, val metaId: Long, diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt index 8288b6d95b..d4d1d9e6f2 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -1,10 +1,10 @@ package io.novafoundation.nova.feature_account_api.data.repository -import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxies +import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId interface ProxyRepository { - suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List + suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index d1af37d507..795e064fdb 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -49,6 +49,8 @@ interface AccountRepository { fun allMetaAccountsFlow(): Flow> + fun activeMetaAccountsFlow(): Flow> + fun metaAccountBalancesFlow(): Flow> fun metaAccountBalancesFlow(metaId: Long): Flow> diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt index 0c6195d0c3..fbcb621a32 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt @@ -49,7 +49,7 @@ class SelectedAccountUseCase( } LightMetaAccount.Type.LEDGER -> R.drawable.ic_ledger - LightMetaAccount.Type.PROXIED -> null // TODO Add icon for proxy account + LightMetaAccount.Type.PROXIED -> R.drawable.ic_proxy } SelectedWalletModel( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt index 94e908f87e..686bff4dc6 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt @@ -60,7 +60,7 @@ fun LightMetaAccount( class MetaAccount( override val id: Long, val chainAccounts: Map, - val proxies: List, + val proxy: ProxyAccount?, override val substratePublicKey: ByteArray?, override val substrateCryptoType: CryptoType?, override val substrateAccountId: ByteArray?, diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountAssetBalance.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountAssetBalance.kt index 60752bc1d6..55b761711d 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountAssetBalance.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountAssetBalance.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_api.domain.model import io.novafoundation.nova.feature_currency_api.domain.model.Currency +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigDecimal import java.math.BigInteger @@ -15,6 +16,8 @@ class MetaAccountAssetBalance( class MetaAccountWithTotalBalance( val metaAccount: MetaAccount, + val proxyMetaAccount: MetaAccount?, + val proxyChain: Chain?, val totalBalance: BigDecimal, val currency: Currency, ) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt index f39582168b..211000edce 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt @@ -1,8 +1,11 @@ package io.novafoundation.nova.feature_account_api.domain.model +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId + class ProxyAccount( val metaId: Long, - val proxyAccountId: ByteArray, + val chainId: ChainId, + val proxiedAccountId: ByteArray, val proxyType: ProxyType, ) { diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt index 2ab6f08369..7c7f5bf9a4 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt @@ -5,6 +5,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.core.view.isVisible +import coil.ImageLoader import io.novafoundation.nova.common.list.BaseGroupedDiffCallback import io.novafoundation.nova.common.list.GroupedListAdapter import io.novafoundation.nova.common.list.GroupedListHolder @@ -12,10 +13,12 @@ import io.novafoundation.nova.common.list.PayloadGenerator import io.novafoundation.nova.common.list.resolvePayload import io.novafoundation.nova.common.utils.dp import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.common.utils.letOrHide import io.novafoundation.nova.common.utils.setDrawableStart import io.novafoundation.nova.common.view.ChipLabelModel import io.novafoundation.nova.common.view.ChipLabelView import io.novafoundation.nova.feature_account_api.R +import io.novafoundation.nova.feature_account_api.presenatation.chain.loadChainIcon import kotlinx.android.synthetic.main.item_account.view.itemAccountArrow import kotlinx.android.synthetic.main.item_account.view.itemAccountCheck import kotlinx.android.synthetic.main.item_account.view.itemAccountContainer @@ -23,9 +26,11 @@ import kotlinx.android.synthetic.main.item_account.view.itemAccountDelete import kotlinx.android.synthetic.main.item_account.view.itemAccountIcon import kotlinx.android.synthetic.main.item_account.view.itemAccountSubtitle import kotlinx.android.synthetic.main.item_account.view.itemAccountTitle +import kotlinx.android.synthetic.main.item_account.view.itemChainIcon class AccountsAdapter( private val accountItemHandler: AccountItemHandler, + private val imageLoader: ImageLoader, initialMode: Mode ) : GroupedListAdapter(DiffCallback()) { @@ -57,7 +62,7 @@ class AccountsAdapter( } override fun createChildViewHolder(parent: ViewGroup): GroupedListHolder { - return AccountHolder(parent.inflateChild(R.layout.item_account)) + return AccountHolder(parent.inflateChild(R.layout.item_account), imageLoader) } override fun bindGroup(holder: GroupedListHolder, group: ChipLabelModel) { @@ -102,7 +107,7 @@ class AccountTypeHolder(override val containerView: ChipLabelView) : GroupedList } } -class AccountHolder(view: View) : GroupedListHolder(view) { +class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedListHolder(view) { init { val lt = LayoutTransition().apply { @@ -123,6 +128,9 @@ class AccountHolder(view: View) : GroupedListHolder(view) { bindMode(mode, accountModel, handler) itemAccountIcon.setImageDrawable(accountModel.picture) + itemChainIcon.letOrHide(accountModel.chainIconUrl) { + itemChainIcon.loadChainIcon(it, imageLoader = imageLoader) + } } fun bindName(accountModel: AccountUi) { diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountUi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountUi.kt index 7f3490f42c..1e2869f852 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountUi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountUi.kt @@ -5,9 +5,10 @@ import android.graphics.drawable.Drawable class AccountUi( val id: Long, val title: String, - val subtitle: String, + val subtitle: CharSequence, val isSelected: Boolean, val isClickable: Boolean, val picture: Drawable, + val chainIconUrl: String?, val subtitleIconRes: Int? ) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/wallet/WalletUiUseCase.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/wallet/WalletUiUseCase.kt index 3f32029636..946eaab081 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/wallet/WalletUiUseCase.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/wallet/WalletUiUseCase.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_api.presenatation.account.wallet import android.graphics.drawable.Drawable +import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import kotlinx.coroutines.flow.Flow @@ -19,7 +20,7 @@ interface WalletUiUseCase { suspend fun selectedWalletUi(): WalletModel - suspend fun walletIcon(metaAccount: MetaAccount, transparentBackground: Boolean = true): Drawable + suspend fun walletIcon(metaAccount: MetaAccount, iconSize: Int = AddressIconGenerator.SIZE_MEDIUM, transparentBackground: Boolean = true): Drawable suspend fun walletUiFor(metaAccount: MetaAccount): WalletModel } diff --git a/feature-account-api/src/main/res/layout/item_account.xml b/feature-account-api/src/main/res/layout/item_account.xml index 7cf2e38d37..1138f528f0 100644 --- a/feature-account-api/src/main/res/layout/item_account.xml +++ b/feature-account-api/src/main/res/layout/item_account.xml @@ -6,7 +6,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:animateLayoutChanges="true" - android:background="@drawable/bg_primary_list_item"> + android:background="@drawable/bg_primary_list_item" + tools:background="@color/secondary_screen_background"> + + "${resourceManager.getString(R.string.sr25519_selection_title)} ${ - resourceManager.getString( - R.string.sr25519_selection_subtitle - ) + resourceManager.getString( + R.string.sr25519_selection_subtitle + ) }" CryptoType.ED25519 -> "${resourceManager.getString(R.string.ed25519_selection_title)} ${ - resourceManager.getString( - R.string.ed25519_selection_subtitle - ) + resourceManager.getString( + R.string.ed25519_selection_subtitle + ) }" CryptoType.ECDSA -> "${resourceManager.getString(R.string.ecdsa_selection_title)} ${ - resourceManager.getString( - R.string.ecdsa_selection_subtitle - ) + resourceManager.getString( + R.string.ecdsa_selection_subtitle + ) }" } @@ -127,11 +129,20 @@ fun mapMetaAccountLocalToMetaAccount( } ).filterNotNull() + val proxyAccount = joinedMetaAccountInfo.proxyAccountLocal?.let { + ProxyAccount( + metaId = it.proxyMetaId, + chainId = it.chainId, + proxiedAccountId = it.proxiedAccountId, + proxyType = mapProxyTypeToString(it.proxyType) + ) + } + return with(joinedMetaAccountInfo.metaAccount) { MetaAccount( id = id, chainAccounts = chainAccounts, - proxies = listOf(), // TODO + proxy = proxyAccount, substratePublicKey = substratePublicKey, substrateCryptoType = substrateCryptoType, substrateAccountId = substrateAccountId, @@ -181,3 +192,17 @@ fun mapOptionalNameToNameChooserState(name: String?) = when (name) { null -> AccountNameChooserMixin.State.NoInput else -> AccountNameChooserMixin.State.Input(name) } + +private fun mapProxyTypeToString(proxyType: String): ProxyAccount.ProxyType { + return when (proxyType) { + "Any" -> ProxyAccount.ProxyType.Any + "NonTransfer" -> ProxyAccount.ProxyType.NonTransfer + "Governance" -> ProxyAccount.ProxyType.Governance + "Staking" -> ProxyAccount.ProxyType.Staking + "IdentityJudgement" -> ProxyAccount.ProxyType.IdentityJudgement + "CancelProxy" -> ProxyAccount.ProxyType.CancelProxy + "Auction" -> ProxyAccount.ProxyType.Auction + "NominationPools" -> ProxyAccount.ProxyType.NominationPools + else -> ProxyAccount.ProxyType.Other(proxyType) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 4997369e93..33ca7a21d6 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -2,12 +2,12 @@ package io.novafoundation.nova.feature_account_impl.data.proxy import io.novafoundation.nova.common.address.AccountIdKey import io.novafoundation.nova.common.address.intoKey -import io.novafoundation.nova.common.utils.CollectionDiffer +import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal -import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxies +import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity @@ -34,52 +34,78 @@ class RealProxySyncService( ) : ProxySyncService { override suspend fun startSyncing() { - if (!accounRepository.hasMetaAccounts()) return - val metaAccounts = getMetaAccounts() + if (metaAccounts.isEmpty()) return val supportedProxyChains = getSupportedProxyChains() val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } - // proxiedsWithProxies will be usefull when we union differen proxy types to one account val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) } - val newProxies = proxiedsWithProxies.formatToLocalProxies() val oldProxies = accountDao.getAllProxyAccounts() - val proxiesDiff = CollectionDiffer.findDiff(newProxies, oldProxies, forceUseNewItems = false) - - insertMetaAndChainAccounts(proxiesDiff) - insertProxies(proxiesDiff) - } - - private suspend fun insertMetaAndChainAccounts(proxiesDiff: CollectionDiffer.Diff) { - val identitiesByChain = proxiesDiff.added.loadProxiedIdentities() + val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) - val chainAccounts = proxiesDiff.added.map { proxy -> - val identity = identitiesByChain[proxy.chainId]?.get(proxy.proxiedAccountId.intoKey()) + val identitiesByChain = notAddedProxies.loadProxiedIdentities() + val proxiedsToMetaId = notAddedProxies.map { + val identity = identitiesByChain[it.proxied.chainId]?.get(it.proxied.accountId.intoKey()) val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> - createMetaAccount(proxy.chainId, proxy.proxiedAccountId, identity, nextPosition) + createMetaAccount(it.proxied.chainId, it.proxy.metaId, it.proxied.accountId, identity, nextPosition) } - createChainAccount(proxiedMetaId, proxy.chainId, proxy.proxiedAccountId) + it to proxiedMetaId } - accountDao.insertChainAccounts(chainAccounts) - } + val chains = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> + val proxied = proxiedWithProxy.proxied + createChainAccount(proxiedMetaId, proxied.chainId, proxied.accountId) + } - private suspend fun insertProxies(proxiesDiff: CollectionDiffer.Diff) { - val deactivatedProxies = proxiesDiff.removed.map { it.copy(status = ProxyAccountLocal.Status.DEACTIVATED) } + val newProxies = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> + val proxied = proxiedWithProxy.proxied + val proxy = proxiedWithProxy.proxy + createProxyAccount(proxiedMetaId, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) + } + + val deactivatedMetaAccounts = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) - accountDao.insertProxies(proxiesDiff.newOrUpdated) - accountDao.insertProxies(deactivatedProxies) + accountDao.insertChainAccounts(chains) + accountDao.insertProxies(newProxies) + accountDao.changeAccountsStatus(deactivatedMetaAccounts, MetaAccountLocal.Status.DEACTIVATED) } override suspend fun syncForMetaAccount(metaAccount: MetaAccount) { TODO("provide updater to sync proxy delegators for new added accounts") } + private suspend fun filterNotAddedProxieds( + proxiedsWithProxies: List, + oldProxies: List + ): List { + val oldInditifiers = oldProxies.map { it.identifier }.toSet() + return proxiedsWithProxies.filter { it.toLocalIdentifier() !in oldInditifiers } + } + + private suspend fun getDeactivatedMetaIds( + proxiedsWithProxies: List, + oldProxies: List + ): List { + val newIdentifiers = proxiedsWithProxies.map { it.toLocalIdentifier() }.toSet() + return oldProxies.filter { it.identifier !in newIdentifiers } + .map { it.proxiedMetaId } + } + + private suspend fun getProxiedsToRemove( + oldProxies: List, + proxiedsMetaAccounts: List + ): List { + val proxiedsMetaIds = proxiedsMetaAccounts.mapToSet { it.id } + + return oldProxies.filter { it.proxiedMetaId !in proxiedsMetaIds } + .map { it.proxiedMetaId } + } + private suspend fun getMetaAccounts(): List { return accounRepository.allMetaAccounts() .filter { @@ -89,7 +115,7 @@ class RealProxySyncService( LightMetaAccount.Type.LEDGER, LightMetaAccount.Type.POLKADOT_VAULT -> true - LightMetaAccount.Type.WATCH_ONLY, + LightMetaAccount.Type.WATCH_ONLY -> true LightMetaAccount.Type.PROXIED -> false } } @@ -108,7 +134,13 @@ class RealProxySyncService( } } - private suspend fun createMetaAccount(chainId: ChainId, proxiedAccountId: AccountId, identity: Identity?, position: Int): MetaAccountLocal { + private suspend fun createMetaAccount( + chainId: ChainId, + parentMetaId: Long, + proxiedAccountId: AccountId, + identity: Identity?, + position: Int + ): MetaAccountLocal { val chain = chainRegistry.getChain(chainId) return MetaAccountLocal( substratePublicKey = null, @@ -117,9 +149,11 @@ class RealProxySyncService( ethereumPublicKey = null, ethereumAddress = if (chain.isEthereumBased) proxiedAccountId else null, name = identity?.name ?: chain.addressOf(proxiedAccountId), + parentMetaId = parentMetaId, isSelected = false, position = position, type = MetaAccountLocal.Type.PROXIED, + status = MetaAccountLocal.Status.ACTIVE ) } @@ -134,33 +168,35 @@ class RealProxySyncService( } private fun createProxyAccount( - metaId: Long, + proxiedMetaId: Long, + proxyMetaId: Long, chainId: ChainId, proxiedAccountId: AccountId, proxyType: String ): ProxyAccountLocal { return ProxyAccountLocal( - metaId = metaId, + proxiedMetaId = proxiedMetaId, + proxyMetaId = proxyMetaId, chainId = chainId, proxiedAccountId = proxiedAccountId, - proxyType = proxyType, - status = ProxyAccountLocal.Status.ACTIVE + proxyType = proxyType ) } - private fun List.formatToLocalProxies(): List { - return flatMap { proxied -> - proxied.proxies.map { proxy -> - createProxyAccount(proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) + private suspend fun List.loadProxiedIdentities(): Map> { + return this.groupBy { it.proxied.chainId } + .mapValues { (chainId, proxiedWithProxies) -> + val proxiedAccountIds = proxiedWithProxies.map { it.proxied.accountId } + identityProvider.identitiesFor(proxiedAccountIds, chainId) } - } } - private suspend fun List.loadProxiedIdentities(): Map> { - return this.groupBy { it.chainId } - .mapValues { (chainId, proxiesWithProxieds) -> - val proxiedAccountIds = proxiesWithProxieds.map { it.proxiedAccountId } - identityProvider.identitiesFor(proxiedAccountIds, chainId) - } + private fun ProxiedWithProxy.toLocalIdentifier(): String { + return ProxyAccountLocal.makeIdentifier( + proxy.metaId, + proxied.chainId, + proxied.accountId, + proxy.proxyType + ) } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService3.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService3.kt new file mode 100644 index 0000000000..95d8256565 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService3.kt @@ -0,0 +1,182 @@ +package io.novafoundation.nova.feature_account_impl.data.proxy + +import android.util.Log +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.common.address.intoKey +import io.novafoundation.nova.common.utils.CollectionDiffer +import io.novafoundation.nova.common.utils.LOG_TAG +import io.novafoundation.nova.core_db.dao.MetaAccountDao +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal +import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity +import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId +import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn +import io.novafoundation.nova.runtime.ext.addressOf +import io.novafoundation.nova.runtime.ext.isSubstrateBased +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.findChains +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class RealProxySyncService2( + private val chainRegistry: ChainRegistry, + private val proxyRepository: ProxyRepository, + private val accounRepository: AccountRepository, + private val accountDao: MetaAccountDao, + private val identityProvider: IdentityProvider +) : ProxySyncService { + + override suspend fun startSyncing() { + val metaAccounts = getMetaAccounts() + if (metaAccounts.isEmpty()) return + + try { + val supportedProxyChains = getSupportedProxyChains() + val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } + + val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> + proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) + } + + val newProxies = proxiedsWithProxies.formatToLocalProxies() + val oldProxies = accountDao.getAllProxyAccounts() + + val proxiesDiff = CollectionDiffer.findDiff(newProxies, oldProxies, forceUseNewItems = false) + + insertMetaAndChainAccounts(proxiesDiff) + insertProxies(proxiesDiff) + deactivateRemovedProxies(metaAccounts, proxiesDiff) + } catch (e: Exception) { + Log.e(LOG_TAG, "Failed to sync proxies", e) + } + } + + override suspend fun syncForMetaAccount(metaAccount: MetaAccount) { + TODO("provide updater to sync proxy delegators for new added accounts") + } + + private suspend fun deactivateRemovedProxies(metaAccounts: List, proxiesDiff: CollectionDiffer.Diff) { + val proxiesToDeactivate = proxiesDiff.removed.map { it.proxyMetaId } + val metaAccountsToDeactivate = metaAccounts.filter { it.proxy?.metaId in proxiesToDeactivate } + .map { it.id } + + accountDao.changeAccountsStatus(metaAccountsToDeactivate, MetaAccountLocal.Status.DEACTIVATED) + } + + private suspend fun insertMetaAndChainAccounts(proxiesDiff: CollectionDiffer.Diff) { + val identitiesByChain = proxiesDiff.added.loadProxiedIdentities() + + val chainAccounts = proxiesDiff.added.map { proxy -> + val identity = identitiesByChain[proxy.chainId]?.get(proxy.proxiedAccountId.intoKey()) + val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> + createMetaAccount(proxy.chainId, proxy.proxyMetaId, proxy.proxiedAccountId, identity, nextPosition) + } + createChainAccount(proxiedMetaId, proxy.chainId, proxy.proxiedAccountId) + } + + accountDao.insertChainAccounts(chainAccounts) + } + + private suspend fun insertProxies(proxiesDiff: CollectionDiffer.Diff) { + accountDao.insertProxies(proxiesDiff.newOrUpdated) + } + + private suspend fun getMetaAccounts(): List { + return accounRepository.allMetaAccounts() + .filter { + when (it.type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.LEDGER, + LightMetaAccount.Type.POLKADOT_VAULT -> true + + LightMetaAccount.Type.WATCH_ONLY, + LightMetaAccount.Type.PROXIED -> false + } + } + } + + private suspend fun getSupportedProxyChains(): List { + return chainRegistry.findChains { it.supportProxy } + } + + private suspend fun Chain.getAvailableAccountIds(metaAccounts: List): List { + return metaAccounts.mapNotNull { metaAccount -> + val accountId = metaAccount.accountIdIn(chain = this) + accountId?.let { + MetaAccountId(accountId, metaAccount.id) + } + } + } + + private suspend fun createMetaAccount( + chainId: ChainId, + parentMetaId: Long, + proxiedAccountId: AccountId, + identity: Identity?, + position: Int + ): MetaAccountLocal { + val chain = chainRegistry.getChain(chainId) + return MetaAccountLocal( + substratePublicKey = null, + substrateCryptoType = null, + substrateAccountId = if (chain.isSubstrateBased) proxiedAccountId else null, + ethereumPublicKey = null, + ethereumAddress = if (chain.isEthereumBased) proxiedAccountId else null, + name = identity?.name ?: chain.addressOf(proxiedAccountId), + parentMetaId = parentMetaId, + isSelected = false, + position = position, + type = MetaAccountLocal.Type.PROXIED, + status = MetaAccountLocal.Status.ACTIVE + ) + } + + private suspend fun createChainAccount(metaId: Long, chainId: ChainId, accountId: AccountId): ChainAccountLocal { + return ChainAccountLocal( + metaId = metaId, + chainId = chainId, + publicKey = null, + accountId = accountId, + cryptoType = null + ) + } + + private fun createProxyAccount( + metaId: Long, + chainId: ChainId, + proxiedAccountId: AccountId, + proxyType: String + ): ProxyAccountLocal { + return ProxyAccountLocal( + proxiedMetaId = metaId, + proxyMetaId = metaId, + chainId = chainId, + proxiedAccountId = proxiedAccountId, + proxyType = proxyType + ) + } + + private fun List.formatToLocalProxies(): List { + return map { + createProxyAccount(it.proxy.metaId, it.proxied.chainId, it.proxied.accountId, it.proxy.proxyType) + } + } + + private suspend fun List.loadProxiedIdentities(): Map> { + return this.groupBy { it.chainId } + .mapValues { (chainId, proxiesWithProxieds) -> + val proxiedAccountIds = proxiesWithProxieds.map { it.proxiedAccountId } + identityProvider.identitiesFor(proxiedAccountIds, chainId) + } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index aadf20f212..30b81723a2 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -140,6 +140,10 @@ class AccountRepositoryImpl( return accountDataSource.allMetaAccountsFlow() } + override fun activeMetaAccountsFlow(): Flow> { + return accountDataSource.activeMetaAccountsFlow() + } + override fun metaAccountBalancesFlow(): Flow> { return accountDataSource.metaAccountsWithBalancesFlow() } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt index 99e9288673..d2ef4d0fac 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt @@ -32,9 +32,11 @@ class RealParitySignerRepository( ethereumPublicKey = null, ethereumAddress = null, name = name, + parentMetaId = null, isSelected = false, position = accountDao.nextAccountPosition(), - type = variant.asMetaAccountTypeLocal() + type = variant.asMetaAccountTypeLocal(), + status = MetaAccountLocal.Status.ACTIVE ) return accountDao.insertMetaAccount(metaAccount) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index 2f8a002cc3..ca5618a1ca 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -7,7 +7,7 @@ import io.novafoundation.nova.common.data.network.runtime.binding.castToList import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct import io.novafoundation.nova.common.data.network.runtime.binding.getTyped import io.novafoundation.nova.common.utils.Modules -import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxies +import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId @@ -20,7 +20,7 @@ class RealProxyRepository( private val remoteSource: StorageDataSource ) : ProxyRepository { - override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List { + override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List { val delegatorToProxies = receiveAllProxies(chainId) val accountIdToMetaAccounts = metaAccountIds.associateBy { it.accountId.intoKey() } @@ -32,8 +32,8 @@ class RealProxyRepository( if (matchedProxies.isEmpty()) return@mapNotNull null delegator to matchedProxies - }.map { (delegator, proxies) -> - mapToProxiedWithProxies(chainId, delegator, proxies) + }.flatMap { (delegator, proxies) -> + proxies.map { proxy -> mapToProxiedWithProxies(chainId, delegator, proxy) } } } @@ -66,23 +66,25 @@ class RealProxyRepository( private fun mapToProxiedWithProxies( chainId: ChainId, delegator: AccountIdKey, - proxies: List - ): ProxiedWithProxies { - return ProxiedWithProxies( - accountId = delegator.value, - chainId = chainId, - proxies = proxies + proxies: ProxiedWithProxy.Proxy + ): ProxiedWithProxy { + return ProxiedWithProxy( + proxied = ProxiedWithProxy.Proxied( + accountId = delegator.value, + chainId = chainId + ), + proxy = proxies ) } private fun matchProxiesToAccountsAndMap( proxies: Map, accountIdToMetaAccounts: Map - ): List { + ): List { return proxies.mapNotNull { (proxyAccountId, proxyType) -> val matchedAccount = accountIdToMetaAccounts[proxyAccountId] ?: return@mapNotNull null - ProxiedWithProxies.Proxy( + ProxiedWithProxy.Proxy( accountId = proxyAccountId.value, metaId = matchedAccount.metaId, proxyType = proxyType diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt index bf2d673437..847b6b8242 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt @@ -61,9 +61,11 @@ class RealWatchOnlyRepository( ethereumPublicKey = null, ethereumAddress = ethereumAccountId, name = name, + parentMetaId = null, isSelected = false, position = accountDao.nextAccountPosition(), - type = MetaAccountLocal.Type.WATCH_ONLY + type = MetaAccountLocal.Type.WATCH_ONLY, + status = MetaAccountLocal.Status.ACTIVE ) return accountDao.insertMetaAccount(metaAccount) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 1b8d68824a..269cc26a92 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -49,6 +49,8 @@ interface AccountDataSource : SecretStoreV1 { fun allMetaAccountsFlow(): Flow> + fun activeMetaAccountsFlow(): Flow> + fun metaAccountsWithBalancesFlow(): Flow> fun metaAccountBalancesFlow(metaId: Long): Flow> diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 1bb0845c10..9cce988484 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -155,6 +155,13 @@ class AccountDataSourceImpl( } } + override fun activeMetaAccountsFlow(): Flow> { + return metaAccountDao.getJoinedMetaAccountsInfoByStatusFlow(MetaAccountLocal.Status.ACTIVE) + .map { accountsLocal -> + accountsLocal.map(::mapMetaAccountLocalToMetaAccount) + } + } + override fun metaAccountsWithBalancesFlow(): Flow> { return metaAccountDao.metaAccountsWithBalanceFlow().mapList(::mapMetaAccountWithBalanceFromLocal) } @@ -226,9 +233,11 @@ class AccountDataSourceImpl( ethereumPublicKey = ethereumPublicKey, ethereumAddress = ethereumPublicKey?.asEthereumPublicKey()?.toAccountId()?.value, name = name, + parentMetaId = null, isSelected = false, position = metaAccountDao.nextAccountPosition(), - type = MetaAccountLocal.Type.SECRETS + type = MetaAccountLocal.Type.SECRETS, + status = MetaAccountLocal.Status.ACTIVE ) val metaId = metaAccountDao.insertMetaAccount(metaAccountLocal) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index 277b6fa0dd..7996e7c371 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -389,7 +389,8 @@ class AccountFeatureModule { walletUseCase: WalletUiUseCase, metaAccountGroupingInteractor: MetaAccountGroupingInteractor, accountTypePresentationMapper: MetaAccountTypePresentationMapper, - ) = MetaAccountWithBalanceListingMixinFactory(walletUseCase, metaAccountGroupingInteractor, accountTypePresentationMapper) + resourceManager: ResourceManager + ) = MetaAccountWithBalanceListingMixinFactory(walletUseCase, metaAccountGroupingInteractor, accountTypePresentationMapper, resourceManager) @Provides @FeatureScope diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt index 408fa0d86a..505619f8f2 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt @@ -31,15 +31,16 @@ class MetaAccountGroupingInteractorImpl( override fun metaAccountsWithTotalBalanceFlow(): Flow> { return combine( currencyRepository.observeSelectCurrency(), - accountRepository.allMetaAccountsFlow(), - accountRepository.metaAccountBalancesFlow() - ) { selectedCurrency, accounts, allBalances -> + accountRepository.activeMetaAccountsFlow(), + accountRepository.metaAccountBalancesFlow(), + chainRegistry.chainsById + ) { selectedCurrency, accounts, allBalances, chains -> val groupedBalances = allBalances.groupBy(MetaAccountAssetBalance::metaId) accounts.map { metaAccount -> val accountBalances = groupedBalances[metaAccount.id] ?: emptyList() - metaAccountWithTotalBalance(accountBalances, metaAccount, selectedCurrency) + metaAccountWithTotalBalance(accountBalances, metaAccount, accounts, selectedCurrency, chains) } .groupBy { it.metaAccount.type } .toSortedMap(metaAccountTypeComparator()) @@ -49,10 +50,12 @@ class MetaAccountGroupingInteractorImpl( override fun metaAccountWithTotalBalanceFlow(metaId: Long): Flow { return combine( currencyRepository.observeSelectCurrency(), + accountRepository.allMetaAccountsFlow(), accountRepository.metaAccountFlow(metaId), - accountRepository.metaAccountBalancesFlow(metaId) - ) { selectedCurrency, metaAccount, metaAccountBalances -> - metaAccountWithTotalBalance(metaAccountBalances, metaAccount, selectedCurrency) + accountRepository.metaAccountBalancesFlow(metaId), + chainRegistry.chainsById + ) { selectedCurrency, allMetaAccounts, metaAccount, metaAccountBalances, chains -> + metaAccountWithTotalBalance(metaAccountBalances, metaAccount, allMetaAccounts, selectedCurrency, chains) } } @@ -71,10 +74,12 @@ class MetaAccountGroupingInteractorImpl( .any { it.hasAccountIn(destinationChain) } } - private fun metaAccountWithTotalBalance( + private suspend fun metaAccountWithTotalBalance( metaAccountBalances: List, metaAccount: MetaAccount, - selectedCurrency: Currency + allMetaAccounts: List, + selectedCurrency: Currency, + chains: Map ): MetaAccountWithTotalBalance { val totalBalance = metaAccountBalances.sumByBigDecimal { val totalInPlanks = it.freeInPlanks + it.reservedInPlanks + it.offChainBalance.orZero() @@ -82,8 +87,12 @@ class MetaAccountGroupingInteractorImpl( totalInPlanks.amountFromPlanks(it.precision) * it.rate.orZero() } + val proxyMetaAccount = metaAccount.proxy?.let { proxy -> allMetaAccounts.firstOrNull { it.id == proxy.metaId } } + return MetaAccountWithTotalBalance( metaAccount = metaAccount, + proxyMetaAccount = proxyMetaAccount, + proxyChain = metaAccount.proxy?.chainId?.let(chains::getValue), totalBalance = totalBalance, currency = selectedCurrency ) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt index 6368e0456c..83dfad17c7 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt @@ -32,7 +32,10 @@ class MetaAccountTypePresentationMapper( title = resourceManager.getString(R.string.common_ledger) ) - LightMetaAccount.Type.PROXIED -> null // TODO Add icon for proxy account + LightMetaAccount.Type.PROXIED -> ChipLabelModel( + iconRes = R.drawable.ic_proxy, + title = resourceManager.getString(R.string.account_proxieds) + ) } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt index cacafe9e1c..bab6607ea7 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt @@ -79,6 +79,7 @@ private class MetaAccountValidForTransactionListingMixin( isSelected = isSelected, isClickable = chainAddress != null, picture = icon, + chainIconUrl = null, subtitleIconRes = if (chainAddress == null) R.drawable.ic_warning_filled else null ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt index ec3d2776b6..742af17a51 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt @@ -1,12 +1,23 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.common.listing +import android.text.Spannable +import android.text.SpannableStringBuilder import io.novafoundation.nova.common.list.toListWithHeaders +import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.WithCoroutineScopeExtensions +import io.novafoundation.nova.common.utils.appendEnd +import io.novafoundation.nova.common.utils.appendSpace +import io.novafoundation.nova.common.utils.append +import io.novafoundation.nova.common.utils.colorSpan +import io.novafoundation.nova.common.utils.dp +import io.novafoundation.nova.common.utils.drawableSpan import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountWithTotalBalance import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase +import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_currency_api.presentation.formatters.formatAsCurrency import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.map @@ -15,6 +26,7 @@ class MetaAccountWithBalanceListingMixinFactory( private val walletUiUseCase: WalletUiUseCase, private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, private val accountTypePresentationMapper: MetaAccountTypePresentationMapper, + private val resourceManager: ResourceManager ) { fun create( @@ -26,7 +38,8 @@ class MetaAccountWithBalanceListingMixinFactory( metaAccountGroupingInteractor = metaAccountGroupingInteractor, coroutineScope = coroutineScope, isMetaAccountSelected = isMetaAccountSelected, - accountTypePresentationMapper = accountTypePresentationMapper + accountTypePresentationMapper = accountTypePresentationMapper, + resourceManager = resourceManager ) } } @@ -36,6 +49,7 @@ private class MetaAccountWithBalanceListingMixin( private val walletUiUseCase: WalletUiUseCase, private val isMetaAccountSelected: suspend (MetaAccount) -> Boolean, private val accountTypePresentationMapper: MetaAccountTypePresentationMapper, + private val resourceManager: ResourceManager, coroutineScope: CoroutineScope, ) : MetaAccountListingMixin, WithCoroutineScopeExtensions by WithCoroutineScopeExtensions(coroutineScope) { @@ -49,13 +63,44 @@ private class MetaAccountWithBalanceListingMixin( private suspend fun mapMetaAccountToUi(metaAccountWithBalance: MetaAccountWithTotalBalance) = with(metaAccountWithBalance) { AccountUi( - id = metaAccountWithBalance.metaAccount.id, - title = metaAccountWithBalance.metaAccount.name, - subtitle = totalBalance.formatAsCurrency(metaAccountWithBalance.currency), - isSelected = isMetaAccountSelected(metaAccountWithBalance.metaAccount), + id = metaAccount.id, + title = metaAccount.name, + subtitle = mapSubtitle(this), + isSelected = isMetaAccountSelected(metaAccount), isClickable = true, - picture = walletUiUseCase.walletIcon(metaAccountWithBalance.metaAccount), + picture = walletUiUseCase.walletIcon(metaAccount), + chainIconUrl = proxyChain?.icon, subtitleIconRes = null, ) } + + private suspend fun mapSubtitle( + metaAccountWithBalance: MetaAccountWithTotalBalance + ): CharSequence = with(metaAccountWithBalance) { + when (metaAccount.type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.WATCH_ONLY, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.LEDGER, + LightMetaAccount.Type.POLKADOT_VAULT -> formattedTotalBalance() + + LightMetaAccount.Type.PROXIED -> { + val proxy = metaAccount.proxy ?: return formattedTotalBalance() + val proxyMetaAccount = proxyMetaAccount ?: return formattedTotalBalance() + + val proxyType = mapProxyTypeToString(resourceManager, proxy.proxyType) + val accountIconDrawable = walletUiUseCase.walletIcon(proxyMetaAccount, 16) + + SpannableStringBuilder(resourceManager.getString(R.string.proxy_wallet_subtitle, proxyType)) + .appendSpace() + .appendEnd(drawableSpan(accountIconDrawable)) + .appendSpace() + .append(proxyMetaAccount.name, colorSpan(resourceManager.getColor(R.color.text_primary))) + } + } + } + + private fun MetaAccountWithTotalBalance.formattedTotalBalance(): String { + return totalBalance.formatAsCurrency(currency) + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyTypeMapper.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyTypeMapper.kt new file mode 100644 index 0000000000..f9d3a672c4 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyTypeMapper.kt @@ -0,0 +1,21 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.common.listing + +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.capitalize +import io.novafoundation.nova.common.utils.splitCamelCase +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_impl.R + +fun mapProxyTypeToString(resourceManager: ResourceManager, type: ProxyAccount.ProxyType): String { + return when (type) { + ProxyAccount.ProxyType.Any -> resourceManager.getString(R.string.account_proxy_type_any) + ProxyAccount.ProxyType.NonTransfer -> resourceManager.getString(R.string.account_proxy_type_any) + ProxyAccount.ProxyType.Governance -> resourceManager.getString(R.string.account_proxy_type_governance) + ProxyAccount.ProxyType.Staking -> resourceManager.getString(R.string.account_proxy_type_staking) + ProxyAccount.ProxyType.IdentityJudgement -> resourceManager.getString(R.string.account_proxy_type_identity_judgement) + ProxyAccount.ProxyType.CancelProxy -> resourceManager.getString(R.string.account_proxy_type_cancel_proxy) + ProxyAccount.ProxyType.Auction -> resourceManager.getString(R.string.account_proxy_type_auction) + ProxyAccount.ProxyType.NominationPools -> resourceManager.getString(R.string.account_proxy_type_nomination_pools) + is ProxyAccount.ProxyType.Other -> type.name.splitCamelCase().joinToString { it.capitalize() } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt index 8ca735b546..005896395e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt @@ -6,10 +6,12 @@ import android.view.View import android.view.ViewGroup import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import coil.ImageLoader import io.novafoundation.nova.common.base.BaseBottomSheetFragment import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter import io.novafoundation.nova.feature_account_impl.R +import javax.inject.Inject import kotlinx.android.synthetic.main.fragment_wallet_list.walletListBarAction import kotlinx.android.synthetic.main.fragment_wallet_list.walletListContent import kotlinx.android.synthetic.main.fragment_wallet_list.walletListTitle @@ -18,8 +20,11 @@ abstract class WalletListFragment : BaseBottomSheetFragment(), AccountsAdapter.AccountItemHandler { + @Inject + lateinit var imageLoader: ImageLoader + private val adapter by lazy(LazyThreadSafetyMode.NONE) { - AccountsAdapter(this, initialMode = viewModel.mode) + AccountsAdapter(this, imageLoader, initialMode = viewModel.mode) } override fun onCreateView( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt index 29ee647e89..3188de877c 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.managem import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup +import coil.ImageLoader import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.view.dialog.warningDialog @@ -11,12 +12,16 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.listing. import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent +import javax.inject.Inject import kotlinx.android.synthetic.main.fragment_accounts.accountListToolbar import kotlinx.android.synthetic.main.fragment_accounts.accountsList import kotlinx.android.synthetic.main.fragment_accounts.addAccount class WalletManagmentFragment : BaseFragment(), AccountsAdapter.AccountItemHandler { + @Inject + lateinit var imageLoader: ImageLoader + private lateinit var adapter: AccountsAdapter override fun onCreateView( @@ -26,7 +31,7 @@ class WalletManagmentFragment : BaseFragment(), Accoun ) = layoutInflater.inflate(R.layout.fragment_accounts, container, false) override fun initViews() { - adapter = AccountsAdapter(this, initialMode = viewModel.mode.value) + adapter = AccountsAdapter(this, imageLoader, initialMode = viewModel.mode.value) accountsList.setHasFixedSize(true) accountsList.adapter = adapter diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/wallet/WalletUiUseCaseImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/wallet/WalletUiUseCaseImpl.kt index 04d42462af..486f51ca20 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/wallet/WalletUiUseCaseImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/wallet/WalletUiUseCaseImpl.kt @@ -4,6 +4,7 @@ import android.graphics.drawable.Drawable import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.common.address.AddressIconGenerator.Companion.BACKGROUND_DEFAULT import io.novafoundation.nova.common.address.AddressIconGenerator.Companion.BACKGROUND_TRANSPARENT +import io.novafoundation.nova.common.address.AddressIconGenerator.Companion.SIZE_MEDIUM import io.novafoundation.nova.common.utils.ByteArrayComparator import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository @@ -53,39 +54,40 @@ class WalletUiUseCaseImpl( return WalletModel( metaId = metaAccount.id, name = metaAccount.name, - icon = walletIcon(metaAccount) + icon = walletIcon(metaAccount, SIZE_MEDIUM) ) } override suspend fun walletIcon( metaAccount: MetaAccount, + iconSize: Int, transparentBackground: Boolean ): Drawable { val seed = metaAccount.walletIconSeed() - return generateWalletIcon(seed, transparentBackground) + return generateWalletIcon(seed, iconSize, transparentBackground) } override suspend fun walletUiFor(metaAccount: MetaAccount): WalletModel { return WalletModel( metaId = metaAccount.id, name = metaAccount.name, - icon = walletIcon(metaAccount, transparentBackground = true) + icon = walletIcon(metaAccount, SIZE_MEDIUM, transparentBackground = true) ) } private suspend fun maybeGenerateIcon(accountId: AccountId, shouldGenerate: Boolean): Drawable? { return if (shouldGenerate) { - generateWalletIcon(seed = accountId, transparentBackground = true) + generateWalletIcon(seed = accountId, iconSize = SIZE_MEDIUM, transparentBackground = true) } else { null } } - private suspend fun generateWalletIcon(seed: ByteArray, transparentBackground: Boolean): Drawable { + private suspend fun generateWalletIcon(seed: ByteArray, iconSize: Int, transparentBackground: Boolean): Drawable { return addressIconGenerator.createAddressIcon( accountId = seed, - sizeInDp = AddressIconGenerator.SIZE_MEDIUM, + sizeInDp = iconSize, backgroundColorRes = if (transparentBackground) BACKGROUND_TRANSPARENT else BACKGROUND_DEFAULT ) } diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt index fb61f93579..e06f39c009 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt @@ -48,9 +48,11 @@ class RealLedgerRepository( ethereumPublicKey = null, ethereumAddress = null, name = name, + parentMetaId = null, isSelected = false, position = metaAccountDao.nextAccountPosition(), - type = MetaAccountLocal.Type.LEDGER + type = MetaAccountLocal.Type.LEDGER, + status = MetaAccountLocal.Status.ACTIVE ) val metaId = metaAccountDao.insertMetaAndChainAccounts(metaAccount) { metaId -> diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt index 934835eac1..565674201e 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import androidx.core.os.bundleOf import androidx.recyclerview.widget.ConcatAdapter +import coil.ImageLoader import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.utils.applyStatusBarInsets import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi @@ -30,7 +31,10 @@ abstract class SelectAddressLedgerFragment : fun getBundle(payload: SelectLedgerAddressPayload) = bundleOf(PAYLOAD_KEY to payload) } - private val addressesAdapter = AccountsAdapter(this, AccountsAdapter.Mode.VIEW) + @Inject + protected lateinit var imageLoader: ImageLoader + + private val addressesAdapter by lazy(LazyThreadSafetyMode.NONE) { AccountsAdapter(this, imageLoader, AccountsAdapter.Mode.VIEW) } private val loadMoreAdapter = LedgerSelectAddressLoadMoreAdapter(handler = this, lifecycleOwner = this) @Inject diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt index 8f940f1a9c..fde3cc1a50 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt @@ -157,6 +157,7 @@ abstract class SelectAddressLedgerViewModel( isSelected = false, isClickable = true, picture = addressModel.image, + chainIconUrl = null, subtitleIconRes = null ) } From 2eff6262ff6bc96b4f1b66024d2a2aeac6e95fe4 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 5 Dec 2023 02:34:59 +0100 Subject: [PATCH 012/100] Fixed migration --- .../nova/core_db/dao/MetaAccountDao.kt | 2 +- .../migrations/53_54_AddProxyAccount.kt | 13 +- .../model/chain/account/MetaAccountLocal.kt | 11 +- .../data/proxy/RealProxySyncService.kt | 2 +- .../data/proxy/RealProxySyncService3.kt | 182 ------------------ 5 files changed, 7 insertions(+), 203 deletions(-) delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService3.kt diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 1d8518cbd9..3aea7a5713 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -154,7 +154,7 @@ interface MetaAccountDao { @Query("UPDATE meta_accounts SET name = :newName WHERE id = :metaId") suspend fun updateName(metaId: Long, newName: String) - @Query("DELETE FROM meta_accounts WHERE id = :metaId") + @Query("DELETE FROM meta_accounts WHERE id = :metaId OR parentMetaId = :metaId") suspend fun delete(metaId: Long) @Query("SELECT COALESCE(MAX(position), 0) + 1 FROM meta_accounts") diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt index 89a2f2ecb1..c18a41e9e1 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt @@ -15,19 +15,14 @@ val AddProxyAccount_53_54 = object : Migration(53, 54) { `chainId` TEXT NOT NULL, `proxiedAccountId` BLOB NOT NULL, `proxyType` TEXT NOT NULL, - PRIMARY KEY(`metaId`, `proxiedAccountId`, `chainId`, `proxyType`), + PRIMARY KEY(`proxyMetaId`, `proxiedAccountId`, `chainId`, `proxyType`), FOREIGN KEY(`proxiedMetaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`proxyMetaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE ) """.trimMargin() ) - database.execSQL( - """ - ALTER TABLE 'meta_accounts' ADD COLUMN `status` TEXT NOT NULL DEFAULT ${MetaAccountLocal.Status.ACTIVE}, - ADD COLUMN `parentMetaId` INTEGER, - FOREIGN KEY(`parentMetaId`) REFERENCES `meta_accounts`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE - """.trimMargin() - ) - database.execSQL("ALTER TABLE 'chains' ADD COLUMN `supportProxy` INTEGER NOT NULL DEFAULT 0") + database.execSQL("ALTER TABLE `meta_accounts` ADD COLUMN `status` TEXT NOT NULL DEFAULT 'ACTIVE'") + database.execSQL("ALTER TABLE `meta_accounts` ADD COLUMN `parentMetaId` INTEGER") + database.execSQL("ALTER TABLE `chains` ADD COLUMN `supportProxy` INTEGER NOT NULL DEFAULT 0") } } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt index e469ea8e6d..3effec0e0b 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt @@ -9,18 +9,9 @@ import io.novafoundation.nova.core.model.CryptoType @Entity( tableName = MetaAccountLocal.TABLE_NAME, - foreignKeys = [ - ForeignKey( - parentColumns = ["id"], - childColumns = ["parentMetaId"], - entity = MetaAccountLocal::class, - onDelete = ForeignKey.CASCADE - ) - ], indices = [ Index(value = ["substrateAccountId"]), - Index(value = ["ethereumAddress"]), - Index(value = ["parentMetaId"]) + Index(value = ["ethereumAddress"]) ] ) class MetaAccountLocal( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 33ca7a21d6..41baa52219 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -115,7 +115,7 @@ class RealProxySyncService( LightMetaAccount.Type.LEDGER, LightMetaAccount.Type.POLKADOT_VAULT -> true - LightMetaAccount.Type.WATCH_ONLY -> true + LightMetaAccount.Type.WATCH_ONLY, LightMetaAccount.Type.PROXIED -> false } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService3.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService3.kt deleted file mode 100644 index 95d8256565..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService3.kt +++ /dev/null @@ -1,182 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.data.proxy - -import android.util.Log -import io.novafoundation.nova.common.address.AccountIdKey -import io.novafoundation.nova.common.address.intoKey -import io.novafoundation.nova.common.utils.CollectionDiffer -import io.novafoundation.nova.common.utils.LOG_TAG -import io.novafoundation.nova.core_db.dao.MetaAccountDao -import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal -import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal -import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal -import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy -import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService -import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository -import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity -import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId -import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn -import io.novafoundation.nova.runtime.ext.addressOf -import io.novafoundation.nova.runtime.ext.isSubstrateBased -import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import io.novafoundation.nova.runtime.multiNetwork.findChains -import jp.co.soramitsu.fearless_utils.runtime.AccountId - -class RealProxySyncService2( - private val chainRegistry: ChainRegistry, - private val proxyRepository: ProxyRepository, - private val accounRepository: AccountRepository, - private val accountDao: MetaAccountDao, - private val identityProvider: IdentityProvider -) : ProxySyncService { - - override suspend fun startSyncing() { - val metaAccounts = getMetaAccounts() - if (metaAccounts.isEmpty()) return - - try { - val supportedProxyChains = getSupportedProxyChains() - val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } - - val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> - proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) - } - - val newProxies = proxiedsWithProxies.formatToLocalProxies() - val oldProxies = accountDao.getAllProxyAccounts() - - val proxiesDiff = CollectionDiffer.findDiff(newProxies, oldProxies, forceUseNewItems = false) - - insertMetaAndChainAccounts(proxiesDiff) - insertProxies(proxiesDiff) - deactivateRemovedProxies(metaAccounts, proxiesDiff) - } catch (e: Exception) { - Log.e(LOG_TAG, "Failed to sync proxies", e) - } - } - - override suspend fun syncForMetaAccount(metaAccount: MetaAccount) { - TODO("provide updater to sync proxy delegators for new added accounts") - } - - private suspend fun deactivateRemovedProxies(metaAccounts: List, proxiesDiff: CollectionDiffer.Diff) { - val proxiesToDeactivate = proxiesDiff.removed.map { it.proxyMetaId } - val metaAccountsToDeactivate = metaAccounts.filter { it.proxy?.metaId in proxiesToDeactivate } - .map { it.id } - - accountDao.changeAccountsStatus(metaAccountsToDeactivate, MetaAccountLocal.Status.DEACTIVATED) - } - - private suspend fun insertMetaAndChainAccounts(proxiesDiff: CollectionDiffer.Diff) { - val identitiesByChain = proxiesDiff.added.loadProxiedIdentities() - - val chainAccounts = proxiesDiff.added.map { proxy -> - val identity = identitiesByChain[proxy.chainId]?.get(proxy.proxiedAccountId.intoKey()) - val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> - createMetaAccount(proxy.chainId, proxy.proxyMetaId, proxy.proxiedAccountId, identity, nextPosition) - } - createChainAccount(proxiedMetaId, proxy.chainId, proxy.proxiedAccountId) - } - - accountDao.insertChainAccounts(chainAccounts) - } - - private suspend fun insertProxies(proxiesDiff: CollectionDiffer.Diff) { - accountDao.insertProxies(proxiesDiff.newOrUpdated) - } - - private suspend fun getMetaAccounts(): List { - return accounRepository.allMetaAccounts() - .filter { - when (it.type) { - LightMetaAccount.Type.SECRETS, - LightMetaAccount.Type.PARITY_SIGNER, - LightMetaAccount.Type.LEDGER, - LightMetaAccount.Type.POLKADOT_VAULT -> true - - LightMetaAccount.Type.WATCH_ONLY, - LightMetaAccount.Type.PROXIED -> false - } - } - } - - private suspend fun getSupportedProxyChains(): List { - return chainRegistry.findChains { it.supportProxy } - } - - private suspend fun Chain.getAvailableAccountIds(metaAccounts: List): List { - return metaAccounts.mapNotNull { metaAccount -> - val accountId = metaAccount.accountIdIn(chain = this) - accountId?.let { - MetaAccountId(accountId, metaAccount.id) - } - } - } - - private suspend fun createMetaAccount( - chainId: ChainId, - parentMetaId: Long, - proxiedAccountId: AccountId, - identity: Identity?, - position: Int - ): MetaAccountLocal { - val chain = chainRegistry.getChain(chainId) - return MetaAccountLocal( - substratePublicKey = null, - substrateCryptoType = null, - substrateAccountId = if (chain.isSubstrateBased) proxiedAccountId else null, - ethereumPublicKey = null, - ethereumAddress = if (chain.isEthereumBased) proxiedAccountId else null, - name = identity?.name ?: chain.addressOf(proxiedAccountId), - parentMetaId = parentMetaId, - isSelected = false, - position = position, - type = MetaAccountLocal.Type.PROXIED, - status = MetaAccountLocal.Status.ACTIVE - ) - } - - private suspend fun createChainAccount(metaId: Long, chainId: ChainId, accountId: AccountId): ChainAccountLocal { - return ChainAccountLocal( - metaId = metaId, - chainId = chainId, - publicKey = null, - accountId = accountId, - cryptoType = null - ) - } - - private fun createProxyAccount( - metaId: Long, - chainId: ChainId, - proxiedAccountId: AccountId, - proxyType: String - ): ProxyAccountLocal { - return ProxyAccountLocal( - proxiedMetaId = metaId, - proxyMetaId = metaId, - chainId = chainId, - proxiedAccountId = proxiedAccountId, - proxyType = proxyType - ) - } - - private fun List.formatToLocalProxies(): List { - return map { - createProxyAccount(it.proxy.metaId, it.proxied.chainId, it.proxied.accountId, it.proxy.proxyType) - } - } - - private suspend fun List.loadProxiedIdentities(): Map> { - return this.groupBy { it.chainId } - .mapValues { (chainId, proxiesWithProxieds) -> - val proxiedAccountIds = proxiesWithProxieds.map { it.proxiedAccountId } - identityProvider.identitiesFor(proxiedAccountIds, chainId) - } - } -} From cd994ab7b8dc3a060511068ec95f84ef8d7aca9e Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 5 Dec 2023 02:41:40 +0100 Subject: [PATCH 013/100] Run ktlint --- .../nova/common/utils/SpannableExt.kt | 1 - .../converters/ProxyAccountConverters.kt | 1 - .../migrations/53_54_AddProxyAccount.kt | 1 - .../model/chain/account/MetaAccountLocal.kt | 1 - .../model/chain/account/ProxyAccountLocal.kt | 1 - .../data/mappers/Mappers.kt | 19 +++++++++---------- .../MetaAccountWithBalanceListingMixin.kt | 2 -- 7 files changed, 9 insertions(+), 17 deletions(-) diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt index bd45df61ae..b0d9b86a2f 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/SpannableExt.kt @@ -50,7 +50,6 @@ fun SpannableStringBuilder.appendEnd(span: Any): SpannableStringBuilder { return this } - fun clickableSpan(onClick: () -> Unit) = object : ClickableSpan() { override fun updateDrawState(ds: TextPaint) { ds.isUnderlineText = false diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt index 6239abdda6..8cfbd4a35f 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/converters/ProxyAccountConverters.kt @@ -2,7 +2,6 @@ package io.novafoundation.nova.core_db.converters import androidx.room.TypeConverter import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal -import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal class ProxyAccountConverters { @TypeConverter diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt index c18a41e9e1..cadeb509aa 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt @@ -2,7 +2,6 @@ package io.novafoundation.nova.core_db.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal val AddProxyAccount_53_54 = object : Migration(53, 54) { diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt index 3effec0e0b..00720cc067 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/MetaAccountLocal.kt @@ -2,7 +2,6 @@ package io.novafoundation.nova.core_db.model.chain.account import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.ForeignKey import androidx.room.Index import androidx.room.PrimaryKey import io.novafoundation.nova.core.model.CryptoType diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt index 78dad3ab95..18c62cad67 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/model/chain/account/ProxyAccountLocal.kt @@ -3,7 +3,6 @@ package io.novafoundation.nova.core_db.model.chain.account import androidx.room.Entity import androidx.room.ForeignKey import androidx.room.Ignore -import androidx.room.Index import io.novafoundation.nova.common.utils.Identifiable import jp.co.soramitsu.fearless_utils.extensions.toHexString diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index 2df3ea8cf8..c3d6bd1e02 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -10,7 +10,6 @@ import io.novafoundation.nova.core_db.model.NodeLocal import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.account.JoinedMetaAccountInfo import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal -import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount @@ -40,21 +39,21 @@ fun mapCryptoTypeToCryptoTypeModel( ): CryptoTypeModel { val name = when (encryptionType) { CryptoType.SR25519 -> "${resourceManager.getString(R.string.sr25519_selection_title)} ${ - resourceManager.getString( - R.string.sr25519_selection_subtitle - ) + resourceManager.getString( + R.string.sr25519_selection_subtitle + ) }" CryptoType.ED25519 -> "${resourceManager.getString(R.string.ed25519_selection_title)} ${ - resourceManager.getString( - R.string.ed25519_selection_subtitle - ) + resourceManager.getString( + R.string.ed25519_selection_subtitle + ) }" CryptoType.ECDSA -> "${resourceManager.getString(R.string.ecdsa_selection_title)} ${ - resourceManager.getString( - R.string.ecdsa_selection_subtitle - ) + resourceManager.getString( + R.string.ecdsa_selection_subtitle + ) }" } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt index 742af17a51..9a5374d461 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt @@ -1,6 +1,5 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.common.listing -import android.text.Spannable import android.text.SpannableStringBuilder import io.novafoundation.nova.common.list.toListWithHeaders import io.novafoundation.nova.common.resources.ResourceManager @@ -9,7 +8,6 @@ import io.novafoundation.nova.common.utils.appendEnd import io.novafoundation.nova.common.utils.appendSpace import io.novafoundation.nova.common.utils.append import io.novafoundation.nova.common.utils.colorSpan -import io.novafoundation.nova.common.utils.dp import io.novafoundation.nova.common.utils.drawableSpan import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount From 5f825dc44f1721128b0d5d07f9d193f797486ae0 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 6 Dec 2023 23:10:50 +0100 Subject: [PATCH 014/100] Delegated accounts updates --- .../nova/app/root/navigation/Navigator.kt | 4 + .../main/res/navigation/main_nav_graph.xml | 18 +- .../nova/common/view/ChipLabelView.kt | 2 +- .../shape_account_updated_indicator.xml | 11 + .../res/drawable/shape_updates_indicator.xml | 7 + common/src/main/res/layout/layout_puller.xml | 6 + common/src/main/res/values/strings.xml | 6 + .../data/proxy/MetaAccountsUpdatesRegistry.kt | 20 ++ .../MetaAccountGroupingInteractor.kt | 3 + .../interfaces/SelectedAccountUseCase.kt | 16 +- .../domain/model/MetaAccount.kt | 6 + .../domain/model/MetaAccountAssetBalance.kt | 1 + .../model/ProxiedAndProxyMetaAccount.kt | 9 + .../account/listing/AccountListAdapter.kt | 219 +----------------- .../account/listing/CommonAccountsAdapter.kt | 98 ++++++++ .../listing/MetaAccountPayloadGenerator.kt | 10 + .../listing/holders/AccountChipHolder.kt | 25 ++ .../account/listing/holders/AccountHolder.kt | 131 +++++++++++ .../listing/holders/AccountTitleHolder.kt | 13 ++ .../listing/items/AccountChipGroupRvItem.kt | 12 + .../listing/items/AccountTitleGroupRvItem.kt | 11 + .../account/listing/{ => items}/AccountUi.kt | 4 +- .../view/SelectedWalletView.kt | 10 +- .../src/main/res/layout/item_account.xml | 5 +- .../layout/item_delegated_account_group.xml | 10 + .../main/res/layout/view_selected_wallet.xml | 19 +- .../data/mappers/Mappers.kt | 10 +- .../proxy/RealMetaAccountsUpdatesRegistry.kt | 61 +++++ .../data/proxy/RealProxySyncService.kt | 26 +-- .../di/AccountFeatureComponent.kt | 3 + .../di/AccountFeatureModule.kt | 44 +++- .../MetaAccountGroupingInteractorImpl.kt | 51 +++- .../presentation/AccountRouter.kt | 2 + ...DelegatedMetaAccountUpdatesListingMixin.kt | 86 +++++++ .../MetaAccountTypePresentationMapper.kt | 9 +- ...aAccountValidForTransactionListingMixin.kt | 6 +- .../MetaAccountWithBalanceListingMixin.kt | 28 +-- .../account/common/listing/ProxyFormatter.kt | 49 ++++ .../account/common/listing/ProxyTypeMapper.kt | 21 -- .../account/list/WalletListFragment.kt | 5 +- .../account/list/WalletListViewModel.kt | 4 +- .../DelegatedAccountUpdatesBottomSheet.kt | 54 +++++ .../DelegatedAccountUpdatesViewModel.kt | 33 +++ .../DelegatedAccountsAdapter.kt | 36 +++ .../di/DelegatedAccountUpdatesComponent.kt | 26 +++ .../di/DelegatedAccountUpdatesModule.kt | 41 ++++ .../selectAddress/SelectAddressViewModel.kt | 6 +- .../list/switching/SwitchWalletFragment.kt | 5 + .../list/switching/SwitchWalletViewModel.kt | 18 +- .../list/switching/di/SwitchWalletModule.kt | 3 + .../management/WalletManagmentFragment.kt | 5 +- .../management/WalletManagmentViewModel.kt | 12 +- .../management/di/WalletManagmentModule.kt | 8 +- .../selectWallet/SelectWalletViewModel.kt | 6 +- ...bottom_sheet_delegated_account_updates.xml | 58 +++++ .../SelectAddressLedgerFragment.kt | 7 +- .../SelectAddressLedgerViewModel.kt | 6 +- 57 files changed, 1086 insertions(+), 319 deletions(-) create mode 100644 common/src/main/res/drawable/shape_account_updated_indicator.xml create mode 100644 common/src/main/res/drawable/shape_updates_indicator.xml create mode 100644 common/src/main/res/layout/layout_puller.xml create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/MetaAccountsUpdatesRegistry.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxiedAndProxyMetaAccount.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/CommonAccountsAdapter.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/MetaAccountPayloadGenerator.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountChipHolder.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountTitleHolder.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountChipGroupRvItem.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountTitleGroupRvItem.kt rename feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/{ => items}/AccountUi.kt (80%) create mode 100644 feature-account-api/src/main/res/layout/item_delegated_account_group.xml create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealMetaAccountsUpdatesRegistry.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyTypeMapper.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesBottomSheet.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesViewModel.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountsAdapter.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesComponent.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesModule.kt create mode 100644 feature-account-impl/src/main/res/layout/bottom_sheet_delegated_account_updates.xml diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt index 11573d44a6..18cb6d83ad 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt @@ -284,6 +284,10 @@ class Navigator( navController?.navigate(R.id.action_open_switch_wallet) } + override fun openDelegatedAccountsUpdates() { + navController?.navigate(R.id.action_switchWalletFragment_to_delegatedAccountUpdates) + } + override fun openSelectAddress(arguments: Bundle) { navController?.navigate(R.id.action_open_select_address, arguments) } diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 4a9f47b34d..1868cbc5aa 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -422,11 +422,27 @@ + + + tools:layout="@layout/bottom_sheet_dynamic_list"> + + + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/res/drawable/shape_updates_indicator.xml b/common/src/main/res/drawable/shape_updates_indicator.xml new file mode 100644 index 0000000000..73f40bd947 --- /dev/null +++ b/common/src/main/res/drawable/shape_updates_indicator.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/common/src/main/res/layout/layout_puller.xml b/common/src/main/res/layout/layout_puller.xml new file mode 100644 index 0000000000..2e71bb5c10 --- /dev/null +++ b/common/src/main/res/layout/layout_puller.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 721a6ebe82..0198fd8d0c 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,6 +1,12 @@ + Delegated accounts update + Nova Wallet automatically adds delegated authorities (Proxy) to a separate category for you. You can always manage wallets in Settings. + What is a Proxy? + + Access was revoked + %s proxy: Any Non Transfer diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/MetaAccountsUpdatesRegistry.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/MetaAccountsUpdatesRegistry.kt new file mode 100644 index 0000000000..817a000c2a --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/MetaAccountsUpdatesRegistry.kt @@ -0,0 +1,20 @@ +package io.novafoundation.nova.feature_account_api.data.proxy + +import kotlinx.coroutines.flow.Flow + +interface MetaAccountsUpdatesRegistry { + + fun addMetaIds(ids: List) + + fun observeUpdates(): Flow> + + fun getUpdates(): Set + + fun remove(ids: List) + + fun clear() + + fun hasUpdates(): Boolean + + fun observeUpdatesExist(): Flow +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/MetaAccountGroupingInteractor.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/MetaAccountGroupingInteractor.kt index 65acaa23cd..4319d7c9dd 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/MetaAccountGroupingInteractor.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/MetaAccountGroupingInteractor.kt @@ -4,6 +4,7 @@ import io.novafoundation.nova.common.list.GroupedList import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountWithTotalBalance +import io.novafoundation.nova.feature_account_api.domain.model.ProxiedAndProxyMetaAccount import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow @@ -15,5 +16,7 @@ interface MetaAccountGroupingInteractor { fun getMetaAccountsForTransaction(fromId: ChainId, destinationId: ChainId): Flow> + fun updatedProxieds(): Flow> + suspend fun hasAvailableMetaAccountsForDestination(fromId: ChainId, destinationId: ChainId): Boolean } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt index fbcb621a32..5ae85f9fbd 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/SelectedAccountUseCase.kt @@ -4,6 +4,7 @@ import android.graphics.drawable.Drawable import androidx.annotation.DrawableRes import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.feature_account_api.R +import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.asPolkadotVaultVariantOrThrow @@ -12,12 +13,14 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.polkadot import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map class SelectedWalletModel( @DrawableRes val typeIcon: Int?, val walletIcon: Drawable, val name: String, + val hasUpdates: Boolean, ) class SelectedAccountUseCase( @@ -25,6 +28,7 @@ class SelectedAccountUseCase( private val walletUiUseCase: WalletUiUseCase, private val addressIconGenerator: AddressIconGenerator, private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, + private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry ) { fun selectedMetaAccountFlow(): Flow = accountRepository.selectedMetaAccountFlow() @@ -37,10 +41,13 @@ class SelectedAccountUseCase( ) } - fun selectedWalletModelFlow(): Flow = selectedMetaAccountFlow().map { - val icon = walletUiUseCase.walletIcon(it, transparentBackground = false) + fun selectedWalletModelFlow(): Flow = combine( + selectedMetaAccountFlow(), + metaAccountsUpdatesRegistry.observeUpdatesExist() + ) { metaAccount, hasMetaAccountsUpdates -> + val icon = walletUiUseCase.walletIcon(metaAccount, transparentBackground = false) - val typeIcon = when (val type = it.type) { + val typeIcon = when (val type = metaAccount.type) { LightMetaAccount.Type.SECRETS -> null // no icon for secrets account LightMetaAccount.Type.WATCH_ONLY -> R.drawable.ic_watch_only_filled LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.POLKADOT_VAULT -> { @@ -55,7 +62,8 @@ class SelectedAccountUseCase( SelectedWalletModel( typeIcon = typeIcon, walletIcon = icon, - name = it.name + name = metaAccount.name, + hasUpdates = hasMetaAccountsUpdates ) } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt index 686bff4dc6..bdc6dcf9c9 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt @@ -29,10 +29,15 @@ interface LightMetaAccount { val isSelected: Boolean val name: String val type: Type + val state: LightMetaAccount.State enum class Type { SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, POLKADOT_VAULT, PROXIED } + + enum class State { + ACTIVE, DEACTIVATED + } } fun LightMetaAccount( @@ -69,6 +74,7 @@ class MetaAccount( override val isSelected: Boolean, override val name: String, override val type: LightMetaAccount.Type, + override val state: LightMetaAccount.State ) : LightMetaAccount { class ChainAccount( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountAssetBalance.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountAssetBalance.kt index 55b761711d..e0c19daf65 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountAssetBalance.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountAssetBalance.kt @@ -20,4 +20,5 @@ class MetaAccountWithTotalBalance( val proxyChain: Chain?, val totalBalance: BigDecimal, val currency: Currency, + val hasUpdates: Boolean, ) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxiedAndProxyMetaAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxiedAndProxyMetaAccount.kt new file mode 100644 index 0000000000..039c0369a7 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxiedAndProxyMetaAccount.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.feature_account_api.domain.model + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain + +class ProxiedAndProxyMetaAccount( + val proxied: MetaAccount, + val proxy: MetaAccount, + val chain: Chain +) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt index 7c7f5bf9a4..04c9c45e90 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt @@ -1,215 +1,20 @@ package io.novafoundation.nova.feature_account_api.presenatation.account.listing -import android.animation.LayoutTransition -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import androidx.core.view.isVisible import coil.ImageLoader -import io.novafoundation.nova.common.list.BaseGroupedDiffCallback -import io.novafoundation.nova.common.list.GroupedListAdapter -import io.novafoundation.nova.common.list.GroupedListHolder -import io.novafoundation.nova.common.list.PayloadGenerator -import io.novafoundation.nova.common.list.resolvePayload -import io.novafoundation.nova.common.utils.dp -import io.novafoundation.nova.common.utils.inflateChild -import io.novafoundation.nova.common.utils.letOrHide -import io.novafoundation.nova.common.utils.setDrawableStart -import io.novafoundation.nova.common.view.ChipLabelModel import io.novafoundation.nova.common.view.ChipLabelView -import io.novafoundation.nova.feature_account_api.R -import io.novafoundation.nova.feature_account_api.presenatation.chain.loadChainIcon -import kotlinx.android.synthetic.main.item_account.view.itemAccountArrow -import kotlinx.android.synthetic.main.item_account.view.itemAccountCheck -import kotlinx.android.synthetic.main.item_account.view.itemAccountContainer -import kotlinx.android.synthetic.main.item_account.view.itemAccountDelete -import kotlinx.android.synthetic.main.item_account.view.itemAccountIcon -import kotlinx.android.synthetic.main.item_account.view.itemAccountSubtitle -import kotlinx.android.synthetic.main.item_account.view.itemAccountTitle -import kotlinx.android.synthetic.main.item_account.view.itemChainIcon +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountChipHolder +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountChipGroupRvItem class AccountsAdapter( - private val accountItemHandler: AccountItemHandler, + private val accountItemHandler: AccountHolder.AccountItemHandler, private val imageLoader: ImageLoader, - initialMode: Mode -) : GroupedListAdapter(DiffCallback()) { - - private var mode: Mode = initialMode - - enum class Mode { - VIEW, EDIT, SWITCH - } - - interface AccountItemHandler { - - fun itemClicked(accountModel: AccountUi) - - fun deleteClicked(accountModel: AccountUi) { - // default no op - } - } - - fun setMode(mode: Mode) { - this.mode = mode - - notifyItemRangeChanged(0, itemCount, mode) - } - - override fun createGroupViewHolder(parent: ViewGroup): GroupedListHolder { - val view = ChipLabelView(parent.context) - - return AccountTypeHolder(view) - } - - override fun createChildViewHolder(parent: ViewGroup): GroupedListHolder { - return AccountHolder(parent.inflateChild(R.layout.item_account), imageLoader) - } - - override fun bindGroup(holder: GroupedListHolder, group: ChipLabelModel) { - (holder as AccountTypeHolder).bind(group) - } - - override fun bindChild(holder: GroupedListHolder, child: AccountUi) { - (holder as AccountHolder).bind(mode, child, accountItemHandler) - } - - override fun bindChild(holder: GroupedListHolder, position: Int, child: AccountUi, payloads: List) { - require(holder is AccountHolder) - - resolvePayload( - holder, - position, - payloads, - onUnknownPayload = { holder.bindMode(mode, child, accountItemHandler) }, - onDiffCheck = { - when (it) { - AccountUi::title -> holder.bindName(child) - AccountUi::subtitle -> holder.bindSubtitle(child) - AccountUi::isSelected -> holder.bindMode(mode, child, accountItemHandler) - } - } - ) - } -} - -class AccountTypeHolder(override val containerView: ChipLabelView) : GroupedListHolder(containerView) { - - init { - val context = containerView.context - - containerView.layoutParams = ViewGroup.MarginLayoutParams(WRAP_CONTENT, WRAP_CONTENT).apply { - setMargins(16.dp(context), 16.dp(context), 0, 8.dp(context)) - } - } - - fun bind(item: ChipLabelModel) { - containerView.setModel(item) - } -} - -class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedListHolder(view) { - - init { - val lt = LayoutTransition().apply { - disableTransitionType(LayoutTransition.DISAPPEARING) - disableTransitionType(LayoutTransition.APPEARING) - } - - containerView.itemAccountContainer.layoutTransition = lt - } - - fun bind( - mode: AccountsAdapter.Mode, - accountModel: AccountUi, - handler: AccountsAdapter.AccountItemHandler, - ) = with(containerView) { - bindName(accountModel) - bindSubtitle(accountModel) - bindMode(mode, accountModel, handler) - - itemAccountIcon.setImageDrawable(accountModel.picture) - itemChainIcon.letOrHide(accountModel.chainIconUrl) { - itemChainIcon.loadChainIcon(it, imageLoader = imageLoader) - } - } - - fun bindName(accountModel: AccountUi) { - containerView.itemAccountTitle.text = accountModel.title - } - - fun bindSubtitle(accountModel: AccountUi) { - containerView.itemAccountSubtitle.text = accountModel.subtitle - containerView.itemAccountSubtitle.setDrawableStart(accountModel.subtitleIconRes, paddingInDp = 4) - } - - fun bindMode( - mode: AccountsAdapter.Mode, - accountModel: AccountUi, - handler: AccountsAdapter.AccountItemHandler, - ) = with(containerView) { - when (mode) { - AccountsAdapter.Mode.VIEW -> { - itemAccountArrow.visibility = View.VISIBLE - - itemAccountDelete.visibility = View.GONE - itemAccountDelete.setOnClickListener(null) - - itemAccountCheck.visibility = View.GONE - - setOnClickListener { handler.itemClicked(accountModel) } - } - - AccountsAdapter.Mode.EDIT -> { - itemAccountArrow.visibility = View.INVISIBLE - - itemAccountDelete.visibility = View.VISIBLE - itemAccountDelete.setOnClickListener { handler.deleteClicked(accountModel) } - itemAccountDelete.setImageResource(R.drawable.ic_delete_symbol) - - itemAccountCheck.visibility = View.GONE - - setOnClickListener(null) - } - - AccountsAdapter.Mode.SWITCH -> { - itemAccountArrow.visibility = View.GONE - - itemAccountDelete.visibility = View.GONE - - itemAccountCheck.isVisible = accountModel.isClickable - - itemAccountCheck.isChecked = accountModel.isSelected - - setOnClickListener { handler.itemClicked(accountModel) } - } - } - } -} - -private object MetaAccountPayloadGenerator : PayloadGenerator( - AccountUi::title, - AccountUi::subtitle, - AccountUi::isSelected + initialMode: AccountHolder.Mode +) : CommonAccountsAdapter( + accountItemHandler = accountItemHandler, + imageLoader = imageLoader, + diffCallback = AccountDiffCallback(AccountChipGroupRvItem::class.java), + groupFactory = { AccountChipHolder(ChipLabelView(it.context)) }, + groupBinder = { holder, item -> (holder as AccountChipHolder).bind(item) }, + initialMode = initialMode ) - -private class DiffCallback : BaseGroupedDiffCallback(ChipLabelModel::class.java) { - override fun areGroupItemsTheSame(oldItem: ChipLabelModel, newItem: ChipLabelModel): Boolean { - return oldItem.title == newItem.title - } - - override fun areGroupContentsTheSame(oldItem: ChipLabelModel, newItem: ChipLabelModel): Boolean { - return oldItem.iconRes == newItem.iconRes - } - - override fun areChildItemsTheSame(oldItem: AccountUi, newItem: AccountUi): Boolean { - return oldItem.id == newItem.id - } - - override fun areChildContentsTheSame(oldItem: AccountUi, newItem: AccountUi): Boolean { - return oldItem.title == newItem.title && oldItem.subtitle == newItem.subtitle && oldItem.isSelected == newItem.isSelected - } - - override fun getChildChangePayload(oldItem: AccountUi, newItem: AccountUi): Any? { - return MetaAccountPayloadGenerator.diff(oldItem, newItem) - } -} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/CommonAccountsAdapter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/CommonAccountsAdapter.kt new file mode 100644 index 0000000000..97393df2cd --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/CommonAccountsAdapter.kt @@ -0,0 +1,98 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.listing + +import android.view.ViewGroup +import coil.ImageLoader +import io.novafoundation.nova.common.list.BaseGroupedDiffCallback +import io.novafoundation.nova.common.list.GroupedListAdapter +import io.novafoundation.nova.common.list.GroupedListHolder +import io.novafoundation.nova.common.list.resolvePayload +import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.feature_account_api.R +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi + +fun interface AccountGroupViewHolderFactory { + fun create(parent: ViewGroup): GroupedListHolder +} + +fun interface AccountGroupViewHolderBinder { + fun bind(holder: GroupedListHolder, item: Group) +} + +interface AccountGroupRvItem { + fun isItemTheSame(other: AccountGroupRvItem): Boolean +} + +abstract class CommonAccountsAdapter( + private val accountItemHandler: AccountHolder.AccountItemHandler?, + private val imageLoader: ImageLoader, + private val diffCallback: AccountDiffCallback, + private val groupFactory: AccountGroupViewHolderFactory, + private val groupBinder: AccountGroupViewHolderBinder, + initialMode: AccountHolder.Mode, +) : GroupedListAdapter(diffCallback) { + + private var mode: AccountHolder.Mode = initialMode + + fun setMode(mode: AccountHolder.Mode) { + this.mode = mode + + notifyItemRangeChanged(0, itemCount, mode) + } + + override fun createGroupViewHolder(parent: ViewGroup): GroupedListHolder { + return groupFactory.create(parent) + } + + override fun createChildViewHolder(parent: ViewGroup): GroupedListHolder { + return AccountHolder(parent.inflateChild(R.layout.item_account), imageLoader) + } + + override fun bindGroup(holder: GroupedListHolder, group: Group) { + groupBinder.bind(holder, group) + } + + override fun bindChild(holder: GroupedListHolder, child: AccountUi) { + (holder as AccountHolder).bind(mode, child, accountItemHandler) + } + + override fun bindChild(holder: GroupedListHolder, position: Int, child: AccountUi, payloads: List) { + require(holder is AccountHolder) + + resolvePayload( + holder, + position, + payloads, + onUnknownPayload = { holder.bindMode(mode, child, accountItemHandler) }, + onDiffCheck = { + when (it) { + AccountUi::title -> holder.bindName(child) + AccountUi::subtitle -> holder.bindSubtitle(child) + AccountUi::isSelected -> holder.bindMode(mode, child, accountItemHandler) + } + } + ) + } +} + +class AccountDiffCallback(groupClass: Class) : BaseGroupedDiffCallback(groupClass) { + override fun areGroupItemsTheSame(oldItem: Group, newItem: Group): Boolean { + return oldItem.isItemTheSame(newItem) + } + + override fun areGroupContentsTheSame(oldItem: Group, newItem: Group): Boolean { + return oldItem == newItem + } + + override fun areChildItemsTheSame(oldItem: AccountUi, newItem: AccountUi): Boolean { + return oldItem.id == newItem.id + } + + override fun areChildContentsTheSame(oldItem: AccountUi, newItem: AccountUi): Boolean { + return oldItem.title == newItem.title && oldItem.subtitle == newItem.subtitle && oldItem.isSelected == newItem.isSelected + } + + override fun getChildChangePayload(oldItem: AccountUi, newItem: AccountUi): Any? { + return MetaAccountPayloadGenerator.diff(oldItem, newItem) + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/MetaAccountPayloadGenerator.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/MetaAccountPayloadGenerator.kt new file mode 100644 index 0000000000..0db9601a13 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/MetaAccountPayloadGenerator.kt @@ -0,0 +1,10 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.listing + +import io.novafoundation.nova.common.list.PayloadGenerator +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi + +object MetaAccountPayloadGenerator : PayloadGenerator( + AccountUi::title, + AccountUi::subtitle, + AccountUi::isSelected +) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountChipHolder.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountChipHolder.kt new file mode 100644 index 0000000000..d7e9ddbef8 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountChipHolder.kt @@ -0,0 +1,25 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders + +import android.view.ViewGroup +import io.novafoundation.nova.common.list.GroupedListHolder +import io.novafoundation.nova.common.utils.dp +import io.novafoundation.nova.common.view.ChipLabelView +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountChipGroupRvItem + +class AccountChipHolder(override val containerView: ChipLabelView) : GroupedListHolder(containerView) { + + init { + val context = containerView.context + + containerView.layoutParams = ViewGroup.MarginLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ).apply { + setMargins(16.dp(context), 16.dp(context), 0, 8.dp(context)) + } + } + + fun bind(item: AccountChipGroupRvItem) { + containerView.setModel(item.chipLabelModel) + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt new file mode 100644 index 0000000000..eec79bba6b --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt @@ -0,0 +1,131 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders + +import android.animation.LayoutTransition +import android.view.View +import androidx.core.view.isVisible +import coil.ImageLoader +import io.novafoundation.nova.common.list.GroupedListHolder +import io.novafoundation.nova.common.utils.letOrHide +import io.novafoundation.nova.common.utils.setDrawableEnd +import io.novafoundation.nova.common.utils.setDrawableStart +import io.novafoundation.nova.feature_account_api.R +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.chain.loadChainIcon +import kotlinx.android.synthetic.main.item_account.view.itemAccountArrow +import kotlinx.android.synthetic.main.item_account.view.itemAccountCheck +import kotlinx.android.synthetic.main.item_account.view.itemAccountContainer +import kotlinx.android.synthetic.main.item_account.view.itemAccountDelete +import kotlinx.android.synthetic.main.item_account.view.itemAccountIcon +import kotlinx.android.synthetic.main.item_account.view.itemAccountSubtitle +import kotlinx.android.synthetic.main.item_account.view.itemAccountTitle +import kotlinx.android.synthetic.main.item_account.view.itemChainIcon + +class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedListHolder(view) { + + interface AccountItemHandler { + + fun itemClicked(accountModel: AccountUi) + + fun deleteClicked(accountModel: AccountUi) { + // default no op + } + } + + enum class Mode { + VIEW, OPEN, EDIT, SWITCH + } + + init { + val lt = LayoutTransition().apply { + disableTransitionType(LayoutTransition.DISAPPEARING) + disableTransitionType(LayoutTransition.APPEARING) + } + + containerView.itemAccountContainer.layoutTransition = lt + } + + fun bind( + mode: Mode, + accountModel: AccountUi, + handler: AccountItemHandler?, + ) = with(containerView) { + bindName(accountModel) + bindSubtitle(accountModel) + bindMode(mode, accountModel, handler) + + itemAccountIcon.setImageDrawable(accountModel.picture) + itemChainIcon.letOrHide(accountModel.chainIconUrl) { + itemChainIcon.loadChainIcon(it, imageLoader = imageLoader) + } + + if (accountModel.updateIndicator) { + itemAccountTitle.setDrawableEnd(R.drawable.shape_account_updated_indicator, paddingInDp = 8) + } else { + itemAccountTitle.setDrawableEnd(null) + } + + itemAccountContainer.alpha = if (accountModel.enabled) 1f else 0.56f + } + + fun bindName(accountModel: AccountUi) { + containerView.itemAccountTitle.text = accountModel.title + } + + fun bindSubtitle(accountModel: AccountUi) { + containerView.itemAccountSubtitle.text = accountModel.subtitle + containerView.itemAccountSubtitle.setDrawableStart(accountModel.subtitleIconRes, paddingInDp = 4) + } + + fun bindMode( + mode: Mode, + accountModel: AccountUi, + handler: AccountItemHandler?, + ) = with(containerView) { + when (mode) { + Mode.VIEW -> { + itemAccountArrow.visibility = View.GONE + itemAccountDelete.visibility = View.GONE + itemAccountCheck.visibility = View.GONE + + itemAccountDelete.setOnClickListener(null) + + setOnClickListener(null) + } + + Mode.OPEN -> { + itemAccountArrow.visibility = View.VISIBLE + + itemAccountDelete.visibility = View.GONE + itemAccountDelete.setOnClickListener(null) + + itemAccountCheck.visibility = View.GONE + + setOnClickListener { handler?.itemClicked(accountModel) } + } + + Mode.EDIT -> { + itemAccountArrow.visibility = View.INVISIBLE + + itemAccountDelete.visibility = View.VISIBLE + itemAccountDelete.setOnClickListener { handler?.deleteClicked(accountModel) } + itemAccountDelete.setImageResource(R.drawable.ic_delete_symbol) + + itemAccountCheck.visibility = View.GONE + + setOnClickListener(null) + } + + Mode.SWITCH -> { + itemAccountArrow.visibility = View.GONE + + itemAccountDelete.visibility = View.GONE + + itemAccountCheck.isVisible = accountModel.isClickable + + itemAccountCheck.isChecked = accountModel.isSelected + + setOnClickListener { handler?.itemClicked(accountModel) } + } + } + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountTitleHolder.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountTitleHolder.kt new file mode 100644 index 0000000000..77eed94606 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountTitleHolder.kt @@ -0,0 +1,13 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders + +import android.view.View +import io.novafoundation.nova.common.list.GroupedListHolder +import kotlinx.android.synthetic.main.item_delegated_account_group.view.delegatedAccountGroup +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountTitleGroupRvItem + +class AccountTitleHolder(override val containerView: View) : GroupedListHolder(containerView) { + + fun bind(item: AccountTitleGroupRvItem) { + containerView.delegatedAccountGroup.text = item.title + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountChipGroupRvItem.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountChipGroupRvItem.kt new file mode 100644 index 0000000000..124ffa50cb --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountChipGroupRvItem.kt @@ -0,0 +1,12 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.listing.items + +import io.novafoundation.nova.common.view.ChipLabelModel +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountGroupRvItem + +data class AccountChipGroupRvItem( + val chipLabelModel: ChipLabelModel +) : AccountGroupRvItem { + override fun isItemTheSame(other: AccountGroupRvItem): Boolean { + return other is AccountChipGroupRvItem && chipLabelModel.title == other.chipLabelModel.title + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountTitleGroupRvItem.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountTitleGroupRvItem.kt new file mode 100644 index 0000000000..f6cae308c1 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountTitleGroupRvItem.kt @@ -0,0 +1,11 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.listing.items + +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountGroupRvItem + +data class AccountTitleGroupRvItem( + val title: String +) : AccountGroupRvItem { + override fun isItemTheSame(other: AccountGroupRvItem): Boolean { + return other is AccountTitleGroupRvItem && title == other.title + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountUi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt similarity index 80% rename from feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountUi.kt rename to feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt index 1e2869f852..3c13b4bb06 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountUi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_account_api.presenatation.account.listing +package io.novafoundation.nova.feature_account_api.presenatation.account.listing.items import android.graphics.drawable.Drawable @@ -10,5 +10,7 @@ class AccountUi( val isClickable: Boolean, val picture: Drawable, val chainIconUrl: String?, + val enabled: Boolean, + val updateIndicator: Boolean, val subtitleIconRes: Int? ) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/view/SelectedWalletView.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/view/SelectedWalletView.kt index 72a65df8cb..d09d81495f 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/view/SelectedWalletView.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/view/SelectedWalletView.kt @@ -3,30 +3,32 @@ package io.novafoundation.nova.feature_account_api.view import android.content.Context import android.util.AttributeSet import android.view.View -import android.widget.LinearLayout +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.isVisible import io.novafoundation.nova.common.utils.makeGone import io.novafoundation.nova.common.utils.makeVisible import io.novafoundation.nova.common.view.shape.getRoundedCornerDrawable import io.novafoundation.nova.feature_account_api.R import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedWalletModel import kotlinx.android.synthetic.main.view_selected_wallet.view.viewSelectedWalletAccountIcon +import kotlinx.android.synthetic.main.view_selected_wallet.view.viewSelectedWalletAccountUpdateIndicator import kotlinx.android.synthetic.main.view_selected_wallet.view.viewSelectedWalletTypeIcon class SelectedWalletView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr) { +) : ConstraintLayout(context, attrs, defStyleAttr) { init { View.inflate(context, R.layout.view_selected_wallet, this) - - orientation = HORIZONTAL } fun setModel(model: SelectedWalletModel) { viewSelectedWalletAccountIcon.setImageDrawable(model.walletIcon) + viewSelectedWalletAccountUpdateIndicator.isVisible = model.hasUpdates + if (model.typeIcon != null) { background = context.getRoundedCornerDrawable( fillColorRes = R.color.chips_background, diff --git a/feature-account-api/src/main/res/layout/item_account.xml b/feature-account-api/src/main/res/layout/item_account.xml index 1138f528f0..b79dd2a681 100644 --- a/feature-account-api/src/main/res/layout/item_account.xml +++ b/feature-account-api/src/main/res/layout/item_account.xml @@ -48,7 +48,7 @@ + \ No newline at end of file diff --git a/feature-account-api/src/main/res/layout/view_selected_wallet.xml b/feature-account-api/src/main/res/layout/view_selected_wallet.xml index ca15dfce2b..f51aca4616 100644 --- a/feature-account-api/src/main/res/layout/view_selected_wallet.xml +++ b/feature-account-api/src/main/res/layout/view_selected_wallet.xml @@ -5,15 +5,16 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" tools:orientation="horizontal" - tools:parentTag="android.widget.LinearLayout"> + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> @@ -21,7 +22,17 @@ android:id="@+id/viewSelectedWalletAccountIcon" android:layout_width="40dp" android:layout_height="40dp" - android:layout_gravity="end" + android:layout_marginStart="8dp" + app:layout_constraintStart_toEndOf="@id/viewSelectedWalletTypeIcon" + app:layout_goneMarginStart="0dp" tools:src="@drawable/ic_polkadot_24" /> + \ No newline at end of file diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index c3d6bd1e02..fff6ad0d2f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -149,7 +149,8 @@ fun mapMetaAccountLocalToMetaAccount( ethereumPublicKey = ethereumPublicKey, isSelected = isSelected, name = name, - type = mapMetaAccountTypeFromLocal(type) + type = mapMetaAccountTypeFromLocal(type), + state = mapMetaAccountStateFromLocal(status) ) } } @@ -205,3 +206,10 @@ private fun mapProxyTypeToString(proxyType: String): ProxyAccount.ProxyType { else -> ProxyAccount.ProxyType.Other(proxyType) } } + +private fun mapMetaAccountStateFromLocal(local: MetaAccountLocal.Status): LightMetaAccount.State { + return when (local) { + MetaAccountLocal.Status.ACTIVE -> LightMetaAccount.State.ACTIVE + MetaAccountLocal.Status.DEACTIVATED -> LightMetaAccount.State.DEACTIVATED + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealMetaAccountsUpdatesRegistry.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealMetaAccountsUpdatesRegistry.kt new file mode 100644 index 0000000000..df0bf8f1db --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealMetaAccountsUpdatesRegistry.kt @@ -0,0 +1,61 @@ +package io.novafoundation.nova.feature_account_impl.data.proxy + +import io.novafoundation.nova.common.data.storage.Preferences +import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class RealMetaAccountsUpdatesRegistry( + private val preferences: Preferences +) : MetaAccountsUpdatesRegistry { + + private val KEY = "meta_accounts_changes" + + override fun addMetaIds(ids: List) { + val metaIdsSet = getUpdates() + metaIdsSet.addAll(ids) + val metaIdsJoinedToString = metaIdsSet.joinToString(",") + if (metaIdsJoinedToString.isNotEmpty()) { + preferences.putString(KEY, metaIdsJoinedToString) + } + } + + override fun observeUpdates(): Flow> { + return preferences.keyFlow(KEY) + .map { getUpdates() } + } + + override fun getUpdates(): MutableSet { + val metaIds = preferences.getString(KEY) + if (metaIds.isNullOrEmpty()) return mutableSetOf() + + return metaIds.split(",") + .map { it.toLong() } + .toMutableSet() + } + + override fun remove(ids: List) { + val metaIdsSet = getUpdates() + metaIdsSet.removeAll(ids) + val metaIdsJoinedToString = metaIdsSet.joinToString(",") + + if (metaIdsJoinedToString.isEmpty()) { + preferences.removeField(KEY) + } else { + preferences.putString(KEY, metaIdsJoinedToString) + } + } + + override fun clear() { + preferences.removeField(KEY) + } + + override fun observeUpdatesExist(): Flow { + return preferences.keyFlow(KEY) + .map { hasUpdates() } + } + + override fun hasUpdates(): Boolean { + return preferences.contains(KEY) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 41baa52219..4d68fbe88a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -2,12 +2,12 @@ package io.novafoundation.nova.feature_account_impl.data.proxy import io.novafoundation.nova.common.address.AccountIdKey import io.novafoundation.nova.common.address.intoKey -import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy +import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity @@ -30,7 +30,8 @@ class RealProxySyncService( private val proxyRepository: ProxyRepository, private val accounRepository: AccountRepository, private val accountDao: MetaAccountDao, - private val identityProvider: IdentityProvider + private val identityProvider: IdentityProvider, + private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry ) : ProxySyncService { override suspend fun startSyncing() { @@ -68,11 +69,14 @@ class RealProxySyncService( createProxyAccount(proxiedMetaId, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) } - val deactivatedMetaAccounts = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) + val deactivatedMetaAccountIds = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) accountDao.insertChainAccounts(chains) accountDao.insertProxies(newProxies) - accountDao.changeAccountsStatus(deactivatedMetaAccounts, MetaAccountLocal.Status.DEACTIVATED) + accountDao.changeAccountsStatus(deactivatedMetaAccountIds, MetaAccountLocal.Status.DEACTIVATED) + + val changedMetaIds = proxiedsToMetaId.map { it.second } + deactivatedMetaAccountIds + metaAccountsUpdatesRegistry.addMetaIds(changedMetaIds) } override suspend fun syncForMetaAccount(metaAccount: MetaAccount) { @@ -96,16 +100,6 @@ class RealProxySyncService( .map { it.proxiedMetaId } } - private suspend fun getProxiedsToRemove( - oldProxies: List, - proxiedsMetaAccounts: List - ): List { - val proxiedsMetaIds = proxiedsMetaAccounts.mapToSet { it.id } - - return oldProxies.filter { it.proxiedMetaId !in proxiedsMetaIds } - .map { it.proxiedMetaId } - } - private suspend fun getMetaAccounts(): List { return accounRepository.allMetaAccounts() .filter { @@ -113,9 +107,9 @@ class RealProxySyncService( LightMetaAccount.Type.SECRETS, LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.LEDGER, - LightMetaAccount.Type.POLKADOT_VAULT -> true + LightMetaAccount.Type.POLKADOT_VAULT, + LightMetaAccount.Type.WATCH_ONLY -> true - LightMetaAccount.Type.WATCH_ONLY, LightMetaAccount.Type.PROXIED -> false } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt index d71623ecc0..624415e608 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt @@ -16,6 +16,7 @@ import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.advancedEncryption.di.AdvancedEncryptionComponent import io.novafoundation.nova.feature_account_impl.presentation.account.create.di.CreateAccountComponent import io.novafoundation.nova.feature_account_impl.presentation.account.details.di.AccountDetailsComponent +import io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates.di.DelegatedAccountUpdatesComponent import io.novafoundation.nova.feature_account_impl.presentation.account.list.selectAddress.di.SelectAddressComponent import io.novafoundation.nova.feature_account_impl.presentation.account.list.switching.di.SwitchWalletComponent import io.novafoundation.nova.feature_account_impl.presentation.account.management.di.WalletManagmentComponent @@ -77,6 +78,8 @@ interface AccountFeatureComponent : AccountFeatureApi { fun selectAddressComponentFactory(): SelectAddressComponent.Factory + fun delegatedAccountUpdatesFactory(): DelegatedAccountUpdatesComponent.Factory + fun accountDetailsComponentFactory(): AccountDetailsComponent.Factory fun connectionsComponentFactory(): NodesComponent.Factory diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index 7996e7c371..da5c888f1a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -25,6 +25,7 @@ import io.novafoundation.nova.core_db.dao.NodeDao import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository @@ -73,10 +74,13 @@ import io.novafoundation.nova.feature_account_impl.domain.account.advancedEncryp import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity +import io.novafoundation.nova.feature_account_impl.data.proxy.RealMetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountDetailsInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter +import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountTypePresentationMapper import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountWithBalanceListingMixinFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.ProxyFormatter import io.novafoundation.nova.feature_account_impl.presentation.account.wallet.WalletUiUseCaseImpl import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherMixin import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherProvider @@ -104,6 +108,12 @@ import jp.co.soramitsu.fearless_utils.encrypt.junction.BIP32JunctionDecoder ) class AccountFeatureModule { + @Provides + @FeatureScope + fun provideMetaAccountsUpdatesRegistry( + preferences: Preferences + ): MetaAccountsUpdatesRegistry = RealMetaAccountsUpdatesRegistry(preferences) + @Provides @FeatureScope fun provideProxyRepository( @@ -117,13 +127,15 @@ class AccountFeatureModule { proxyRepository: ProxyRepository, accounRepository: AccountRepository, metaAccountDao: MetaAccountDao, - @OnChainIdentity identityProvider: IdentityProvider + @OnChainIdentity identityProvider: IdentityProvider, + metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry ): ProxySyncService = RealProxySyncService( chainRegistry, proxyRepository, accounRepository, metaAccountDao, - identityProvider + identityProvider, + metaAccountsUpdatesRegistry ) @Provides @@ -267,12 +279,14 @@ class AccountFeatureModule { accountRepository: AccountRepository, addressIconGenerator: AddressIconGenerator, walletUiUseCase: WalletUiUseCase, - polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider + polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, + metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry ) = SelectedAccountUseCase( accountRepository = accountRepository, walletUiUseCase = walletUiUseCase, addressIconGenerator = addressIconGenerator, - polkadotVaultVariantConfigProvider = polkadotVaultVariantConfigProvider + polkadotVaultVariantConfigProvider = polkadotVaultVariantConfigProvider, + metaAccountsUpdatesRegistry = metaAccountsUpdatesRegistry ) @Provides @@ -379,18 +393,36 @@ class AccountFeatureModule { chainRegistry: ChainRegistry, accountRepository: AccountRepository, currencyRepository: CurrencyRepository, + metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry ): MetaAccountGroupingInteractor { - return MetaAccountGroupingInteractorImpl(chainRegistry, accountRepository, currencyRepository) + return MetaAccountGroupingInteractorImpl(chainRegistry, accountRepository, currencyRepository, metaAccountsUpdatesRegistry) } + @Provides + @FeatureScope + fun provideProxyFormatter( + walletUseCase: WalletUiUseCase, + resourceManager: ResourceManager + ) = ProxyFormatter(walletUseCase, resourceManager) + + @Provides + @FeatureScope + fun provideDelegatedMetaAccountUpdatesListingMixinFactory( + walletUseCase: WalletUiUseCase, + metaAccountGroupingInteractor: MetaAccountGroupingInteractor, + proxyFormatter: ProxyFormatter, + resourceManager: ResourceManager + ) = DelegatedMetaAccountUpdatesListingMixinFactory(walletUseCase, metaAccountGroupingInteractor, proxyFormatter, resourceManager) + @Provides @FeatureScope fun provideAccountListingMixinFactory( walletUseCase: WalletUiUseCase, metaAccountGroupingInteractor: MetaAccountGroupingInteractor, + proxyFormatter: ProxyFormatter, accountTypePresentationMapper: MetaAccountTypePresentationMapper, resourceManager: ResourceManager - ) = MetaAccountWithBalanceListingMixinFactory(walletUseCase, metaAccountGroupingInteractor, accountTypePresentationMapper, resourceManager) + ) = MetaAccountWithBalanceListingMixinFactory(walletUseCase, metaAccountGroupingInteractor, accountTypePresentationMapper, proxyFormatter, resourceManager) @Provides @FeatureScope diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt index 505619f8f2..8ba9806218 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt @@ -6,12 +6,14 @@ import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.common.utils.removed import io.novafoundation.nova.common.utils.sumByBigDecimal +import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountWithTotalBalance +import io.novafoundation.nova.feature_account_api.domain.model.ProxiedAndProxyMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.addressIn import io.novafoundation.nova.feature_account_api.domain.model.hasAccountIn import io.novafoundation.nova.feature_currency_api.domain.interfaces.CurrencyRepository @@ -20,12 +22,14 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine class MetaAccountGroupingInteractorImpl( private val chainRegistry: ChainRegistry, private val accountRepository: AccountRepository, private val currencyRepository: CurrencyRepository, + private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, ) : MetaAccountGroupingInteractor { override fun metaAccountsWithTotalBalanceFlow(): Flow> { @@ -33,14 +37,15 @@ class MetaAccountGroupingInteractorImpl( currencyRepository.observeSelectCurrency(), accountRepository.activeMetaAccountsFlow(), accountRepository.metaAccountBalancesFlow(), + metaAccountsUpdatesRegistry.observeUpdates(), chainRegistry.chainsById - ) { selectedCurrency, accounts, allBalances, chains -> + ) { selectedCurrency, accounts, allBalances, updatedMetaAccounts, chains -> val groupedBalances = allBalances.groupBy(MetaAccountAssetBalance::metaId) accounts.map { metaAccount -> val accountBalances = groupedBalances[metaAccount.id] ?: emptyList() - - metaAccountWithTotalBalance(accountBalances, metaAccount, accounts, selectedCurrency, chains) + val hasUpdates = updatedMetaAccounts.contains(metaAccount.id) + metaAccountWithTotalBalance(accountBalances, metaAccount, accounts, selectedCurrency, chains, hasUpdates) } .groupBy { it.metaAccount.type } .toSortedMap(metaAccountTypeComparator()) @@ -55,7 +60,7 @@ class MetaAccountGroupingInteractorImpl( accountRepository.metaAccountBalancesFlow(metaId), chainRegistry.chainsById ) { selectedCurrency, allMetaAccounts, metaAccount, metaAccountBalances, chains -> - metaAccountWithTotalBalance(metaAccountBalances, metaAccount, allMetaAccounts, selectedCurrency, chains) + metaAccountWithTotalBalance(metaAccountBalances, metaAccount, allMetaAccounts, selectedCurrency, chains, false) } } @@ -67,6 +72,26 @@ class MetaAccountGroupingInteractorImpl( .toSortedMap(metaAccountTypeComparator()) } + override fun updatedProxieds(): Flow> { + return combine( + metaAccountsUpdatesRegistry.observeUpdates(), + accountRepository.allMetaAccountsFlow(), + chainRegistry.chainsById + ) { metaIds, metaAccount, chainsById -> + val metaById = metaAccount.associateBy(MetaAccount::id) + metaAccount + .filter { it.type == LightMetaAccount.Type.PROXIED && metaIds.contains(it.id) } + .map { + ProxiedAndProxyMetaAccount( + it, + metaById[it.proxy?.metaId] ?: error("Proxy meta account not found"), + chainsById[it.proxy?.chainId] ?: error("Proxy chain not found") + ) + } + .groupBy { it.proxied.state } + }.catch { emit(emptyMap()) } + } + override suspend fun hasAvailableMetaAccountsForDestination(fromId: ChainId, destinationId: ChainId): Boolean { val fromChain = chainRegistry.getChain(fromId) val destinationChain = chainRegistry.getChain(destinationId) @@ -79,7 +104,8 @@ class MetaAccountGroupingInteractorImpl( metaAccount: MetaAccount, allMetaAccounts: List, selectedCurrency: Currency, - chains: Map + chains: Map, + hasUpdates: Boolean ): MetaAccountWithTotalBalance { val totalBalance = metaAccountBalances.sumByBigDecimal { val totalInPlanks = it.freeInPlanks + it.reservedInPlanks + it.offChainBalance.orZero() @@ -94,7 +120,8 @@ class MetaAccountGroupingInteractorImpl( proxyMetaAccount = proxyMetaAccount, proxyChain = metaAccount.proxy?.chainId?.let(chains::getValue), totalBalance = totalBalance, - currency = selectedCurrency + currency = selectedCurrency, + hasUpdates = hasUpdates ) } @@ -103,7 +130,17 @@ class MetaAccountGroupingInteractorImpl( val fromChainAddress = selectedMetaAccount.addressIn(from) return accountRepository.allMetaAccounts() .removed { fromChainAddress == it.addressIn(destination) } - .filter { it.type != LightMetaAccount.Type.WATCH_ONLY } + .filter { + when (it.type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.POLKADOT_VAULT, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.LEDGER -> true + + LightMetaAccount.Type.PROXIED, + LightMetaAccount.Type.WATCH_ONLY -> false + } + } } private fun metaAccountTypeComparator() = compareBy { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/AccountRouter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/AccountRouter.kt index 0fec5e83c0..9669d77bde 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/AccountRouter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/AccountRouter.kt @@ -32,6 +32,8 @@ interface AccountRouter : SecureRouter, ReturnableRouter { fun openSwitchWallet() + fun openDelegatedAccountsUpdates() + fun openNodes() fun openAddAccount(payload: AddAccountPayload) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt new file mode 100644 index 0000000000..9e64fae939 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt @@ -0,0 +1,86 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.common.listing + +import io.novafoundation.nova.common.list.toListWithHeaders +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.WithCoroutineScopeExtensions +import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxiedAndProxyMetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.requireAddressIn +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountTitleGroupRvItem +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase +import io.novafoundation.nova.feature_account_impl.R +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.map + +class DelegatedMetaAccountUpdatesListingMixinFactory( + private val walletUiUseCase: WalletUiUseCase, + private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, + private val proxyFormatter: ProxyFormatter, + private val resourceManager: ResourceManager +) { + + fun create(coroutineScope: CoroutineScope): MetaAccountListingMixin { + return DelegatedMetaAccountUpdatesListingMixin( + walletUiUseCase = walletUiUseCase, + metaAccountGroupingInteractor = metaAccountGroupingInteractor, + proxyFormatter = proxyFormatter, + resourceManager = resourceManager, + coroutineScope = coroutineScope + ) + } +} + +private class DelegatedMetaAccountUpdatesListingMixin( + private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, + private val walletUiUseCase: WalletUiUseCase, + private val proxyFormatter: ProxyFormatter, + private val resourceManager: ResourceManager, + coroutineScope: CoroutineScope, +) : MetaAccountListingMixin, WithCoroutineScopeExtensions by WithCoroutineScopeExtensions(coroutineScope) { + + override val metaAccountsFlow = metaAccountGroupingInteractor.updatedProxieds() + .map { list -> + list.toListWithHeaders( + keyMapper = { type, _ -> mapHeader(type) }, + valueMapper = { mapProxiedToUi(it) } + ) + } + .shareInBackground() + + private fun mapHeader(state: LightMetaAccount.State): AccountTitleGroupRvItem { + val text = when (state) { + LightMetaAccount.State.ACTIVE -> resourceManager.getString(R.string.account_proxieds) + LightMetaAccount.State.DEACTIVATED -> resourceManager.getString(R.string.proxieds_updates_deactivated_title) + } + + return AccountTitleGroupRvItem(text) + } + + private suspend fun mapProxiedToUi(proxiedWithProxy: ProxiedAndProxyMetaAccount) = with(proxiedWithProxy) { + AccountUi( + id = proxied.id, + title = proxied.name, + subtitle = mapSubtitle(this), + isSelected = false, + isClickable = true, + picture = walletUiUseCase.walletIcon(proxied), + chainIconUrl = proxiedWithProxy.chain.icon, + subtitleIconRes = null, + enabled = proxiedWithProxy.proxied.state == LightMetaAccount.State.ACTIVE, + updateIndicator = false + ) + } + + private suspend fun mapSubtitle( + proxiedWithProxy: ProxiedAndProxyMetaAccount + ): CharSequence { + val proxy = proxiedWithProxy.proxied.proxy ?: return proxiedWithProxy.proxiedAddress() // fallback + return proxyFormatter.mapProxyMetaAccountSubtitle(proxiedWithProxy.proxied, proxy) + } + + private fun ProxiedAndProxyMetaAccount.proxiedAddress(): String { + return proxied.requireAddressIn(chain) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt index 83dfad17c7..6427f9b56b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountTypePresentationMapper.kt @@ -4,6 +4,7 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.view.ChipLabelModel import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.asPolkadotVaultVariantOrThrow +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountChipGroupRvItem import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_account_impl.R @@ -12,13 +13,14 @@ class MetaAccountTypePresentationMapper( private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, ) { - fun mapMetaAccountTypeToUi(type: LightMetaAccount.Type): ChipLabelModel? { - return when (type) { + fun mapMetaAccountTypeToUi(type: LightMetaAccount.Type): AccountChipGroupRvItem? { + val chipLabelModel = when (type) { LightMetaAccount.Type.SECRETS -> null LightMetaAccount.Type.WATCH_ONLY -> ChipLabelModel( iconRes = R.drawable.ic_watch_only_filled, title = resourceManager.getString(R.string.account_watch_only) ) + LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.POLKADOT_VAULT -> { val config = polkadotVaultVariantConfigProvider.variantConfigFor(type.asPolkadotVaultVariantOrThrow()) @@ -27,6 +29,7 @@ class MetaAccountTypePresentationMapper( title = resourceManager.getString(config.common.nameRes) ) } + LightMetaAccount.Type.LEDGER -> ChipLabelModel( iconRes = R.drawable.ic_ledger, title = resourceManager.getString(R.string.common_ledger) @@ -37,5 +40,7 @@ class MetaAccountTypePresentationMapper( title = resourceManager.getString(R.string.account_proxieds) ) } + + return chipLabelModel?.let { AccountChipGroupRvItem(it) } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt index bab6607ea7..3e1bd9de5d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt @@ -7,7 +7,7 @@ import io.novafoundation.nova.common.utils.lazyAsync import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.addressIn -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -80,7 +80,9 @@ private class MetaAccountValidForTransactionListingMixin( isClickable = chainAddress != null, picture = icon, chainIconUrl = null, - subtitleIconRes = if (chainAddress == null) R.drawable.ic_warning_filled else null + subtitleIconRes = if (chainAddress == null) R.drawable.ic_warning_filled else null, + enabled = true, + updateIndicator = false ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt index 9a5374d461..d3b4d9a99f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt @@ -13,7 +13,7 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountG import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountWithTotalBalance -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_currency_api.presentation.formatters.formatAsCurrency @@ -24,6 +24,7 @@ class MetaAccountWithBalanceListingMixinFactory( private val walletUiUseCase: WalletUiUseCase, private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, private val accountTypePresentationMapper: MetaAccountTypePresentationMapper, + private val proxyFormatter: ProxyFormatter, private val resourceManager: ResourceManager ) { @@ -37,6 +38,7 @@ class MetaAccountWithBalanceListingMixinFactory( coroutineScope = coroutineScope, isMetaAccountSelected = isMetaAccountSelected, accountTypePresentationMapper = accountTypePresentationMapper, + proxyFormatter = proxyFormatter, resourceManager = resourceManager ) } @@ -47,6 +49,7 @@ private class MetaAccountWithBalanceListingMixin( private val walletUiUseCase: WalletUiUseCase, private val isMetaAccountSelected: suspend (MetaAccount) -> Boolean, private val accountTypePresentationMapper: MetaAccountTypePresentationMapper, + private val proxyFormatter: ProxyFormatter, private val resourceManager: ResourceManager, coroutineScope: CoroutineScope, ) : MetaAccountListingMixin, WithCoroutineScopeExtensions by WithCoroutineScopeExtensions(coroutineScope) { @@ -69,6 +72,8 @@ private class MetaAccountWithBalanceListingMixin( picture = walletUiUseCase.walletIcon(metaAccount), chainIconUrl = proxyChain?.icon, subtitleIconRes = null, + enabled = true, + updateIndicator = hasUpdates ) } @@ -82,20 +87,17 @@ private class MetaAccountWithBalanceListingMixin( LightMetaAccount.Type.LEDGER, LightMetaAccount.Type.POLKADOT_VAULT -> formattedTotalBalance() - LightMetaAccount.Type.PROXIED -> { - val proxy = metaAccount.proxy ?: return formattedTotalBalance() - val proxyMetaAccount = proxyMetaAccount ?: return formattedTotalBalance() + LightMetaAccount.Type.PROXIED -> mapProxyTypeToSubtitle(metaAccountWithBalance) + } + } - val proxyType = mapProxyTypeToString(resourceManager, proxy.proxyType) - val accountIconDrawable = walletUiUseCase.walletIcon(proxyMetaAccount, 16) + private suspend fun mapProxyTypeToSubtitle( + metaAccountWithBalance: MetaAccountWithTotalBalance + ): CharSequence = with(metaAccountWithBalance) { + val proxy = metaAccount.proxy ?: return formattedTotalBalance() + val proxyMetaAccount = proxyMetaAccount ?: return formattedTotalBalance() - SpannableStringBuilder(resourceManager.getString(R.string.proxy_wallet_subtitle, proxyType)) - .appendSpace() - .appendEnd(drawableSpan(accountIconDrawable)) - .appendSpace() - .append(proxyMetaAccount.name, colorSpan(resourceManager.getColor(R.color.text_primary))) - } - } + return proxyFormatter.mapProxyMetaAccountSubtitle(proxyMetaAccount, proxy) } private fun MetaAccountWithTotalBalance.formattedTotalBalance(): String { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt new file mode 100644 index 0000000000..dab826b822 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt @@ -0,0 +1,49 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.common.listing + +import android.text.SpannableStringBuilder +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.append +import io.novafoundation.nova.common.utils.appendEnd +import io.novafoundation.nova.common.utils.appendSpace +import io.novafoundation.nova.common.utils.capitalize +import io.novafoundation.nova.common.utils.colorSpan +import io.novafoundation.nova.common.utils.drawableSpan +import io.novafoundation.nova.common.utils.splitCamelCase +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase +import io.novafoundation.nova.feature_account_impl.R + +class ProxyFormatter( + private val walletUiUseCase: WalletUiUseCase, + private val resourceManager: ResourceManager, +) { + + suspend fun mapProxyMetaAccountSubtitle( + proxyMetaAccount: MetaAccount, + proxyAccount: ProxyAccount + ): CharSequence { + val proxyType = mapProxyTypeToString(resourceManager, proxyAccount.proxyType) + val accountIconDrawable = walletUiUseCase.walletIcon(proxyMetaAccount, 16) + + return SpannableStringBuilder(resourceManager.getString(R.string.proxy_wallet_subtitle, proxyType)) + .appendSpace() + .appendEnd(drawableSpan(accountIconDrawable)) + .appendSpace() + .append(proxyMetaAccount.name, colorSpan(resourceManager.getColor(R.color.text_primary))) + } + + fun mapProxyTypeToString(resourceManager: ResourceManager, type: ProxyAccount.ProxyType): String { + return when (type) { + ProxyAccount.ProxyType.Any -> resourceManager.getString(R.string.account_proxy_type_any) + ProxyAccount.ProxyType.NonTransfer -> resourceManager.getString(R.string.account_proxy_type_any) + ProxyAccount.ProxyType.Governance -> resourceManager.getString(R.string.account_proxy_type_governance) + ProxyAccount.ProxyType.Staking -> resourceManager.getString(R.string.account_proxy_type_staking) + ProxyAccount.ProxyType.IdentityJudgement -> resourceManager.getString(R.string.account_proxy_type_identity_judgement) + ProxyAccount.ProxyType.CancelProxy -> resourceManager.getString(R.string.account_proxy_type_cancel_proxy) + ProxyAccount.ProxyType.Auction -> resourceManager.getString(R.string.account_proxy_type_auction) + ProxyAccount.ProxyType.NominationPools -> resourceManager.getString(R.string.account_proxy_type_nomination_pools) + is ProxyAccount.ProxyType.Other -> type.name.splitCamelCase().joinToString { it.capitalize() } + } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyTypeMapper.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyTypeMapper.kt deleted file mode 100644 index f9d3a672c4..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyTypeMapper.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.presentation.account.common.listing - -import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.capitalize -import io.novafoundation.nova.common.utils.splitCamelCase -import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount -import io.novafoundation.nova.feature_account_impl.R - -fun mapProxyTypeToString(resourceManager: ResourceManager, type: ProxyAccount.ProxyType): String { - return when (type) { - ProxyAccount.ProxyType.Any -> resourceManager.getString(R.string.account_proxy_type_any) - ProxyAccount.ProxyType.NonTransfer -> resourceManager.getString(R.string.account_proxy_type_any) - ProxyAccount.ProxyType.Governance -> resourceManager.getString(R.string.account_proxy_type_governance) - ProxyAccount.ProxyType.Staking -> resourceManager.getString(R.string.account_proxy_type_staking) - ProxyAccount.ProxyType.IdentityJudgement -> resourceManager.getString(R.string.account_proxy_type_identity_judgement) - ProxyAccount.ProxyType.CancelProxy -> resourceManager.getString(R.string.account_proxy_type_cancel_proxy) - ProxyAccount.ProxyType.Auction -> resourceManager.getString(R.string.account_proxy_type_auction) - ProxyAccount.ProxyType.NominationPools -> resourceManager.getString(R.string.account_proxy_type_nomination_pools) - is ProxyAccount.ProxyType.Other -> type.name.splitCamelCase().joinToString { it.capitalize() } - } -} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt index 005896395e..5ec041d6af 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt @@ -8,8 +8,9 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import coil.ImageLoader import io.novafoundation.nova.common.base.BaseBottomSheetFragment -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder import io.novafoundation.nova.feature_account_impl.R import javax.inject.Inject import kotlinx.android.synthetic.main.fragment_wallet_list.walletListBarAction @@ -18,7 +19,7 @@ import kotlinx.android.synthetic.main.fragment_wallet_list.walletListTitle abstract class WalletListFragment : BaseBottomSheetFragment(), - AccountsAdapter.AccountItemHandler { + AccountHolder.AccountItemHandler { @Inject lateinit var imageLoader: ImageLoader diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListViewModel.kt index e880d5ed47..90e46ff33d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListViewModel.kt @@ -1,8 +1,8 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.list import io.novafoundation.nova.common.base.BaseViewModel -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter.Mode +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder.Mode +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountListingMixin abstract class WalletListViewModel : BaseViewModel() { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesBottomSheet.kt new file mode 100644 index 0000000000..7dae319454 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesBottomSheet.kt @@ -0,0 +1,54 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import coil.ImageLoader +import io.novafoundation.nova.common.base.BaseBottomSheetFragment +import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents +import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi +import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent +import javax.inject.Inject +import kotlinx.android.synthetic.main.bottom_sheet_delegated_account_updates.delegatedAccountUpdatesDone +import kotlinx.android.synthetic.main.bottom_sheet_delegated_account_updates.delegatedAccountUpdatesLink +import kotlinx.android.synthetic.main.bottom_sheet_delegated_account_updates.delegatedAccountUpdatesList + +class DelegatedAccountUpdatesBottomSheet() : BaseBottomSheetFragment() { + + @Inject + lateinit var imageLoader: ImageLoader + + private val adapter by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { DelegatedAccountsAdapter(imageLoader) } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.bottom_sheet_delegated_account_updates, container, false) + } + + override fun initViews() { + delegatedAccountUpdatesLink.setOnClickListener { viewModel.clickAbout() } + delegatedAccountUpdatesDone.setOnClickListener { viewModel.clickDone() } + delegatedAccountUpdatesList.adapter = adapter + } + + override fun inject() { + FeatureUtils.getFeature( + requireContext(), + AccountFeatureApi::class.java + ) + .delegatedAccountUpdatesFactory() + .create(this) + .inject(this) + } + + override fun subscribe(viewModel: DelegatedAccountUpdatesViewModel) { + observeBrowserEvents(viewModel) + viewModel.accounts.observe { adapter.submitList(it) } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesViewModel.kt new file mode 100644 index 0000000000..a52f0a0c70 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesViewModel.kt @@ -0,0 +1,33 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.data.network.AppLinksProvider +import io.novafoundation.nova.common.mixin.api.Browserable +import io.novafoundation.nova.common.utils.Event +import io.novafoundation.nova.common.utils.event +import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter +import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory +import kotlinx.coroutines.flow.Flow + +class DelegatedAccountUpdatesViewModel( + private val delegatedMetaAccountUpdatesListingMixinFactory: DelegatedMetaAccountUpdatesListingMixinFactory, + private val accountRouter: AccountRouter, + private val appLinksProvider: AppLinksProvider, +) : BaseViewModel(), Browserable { + + private val listingMixin = delegatedMetaAccountUpdatesListingMixinFactory.create(viewModelScope) + + val accounts: Flow> = listingMixin.metaAccountsFlow + + override val openBrowserEvent = MutableLiveData>() + + fun clickAbout() { + openBrowserEvent.value = appLinksProvider.wikiBase.event() + } + + fun clickDone() { + accountRouter.back() + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountsAdapter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountsAdapter.kt new file mode 100644 index 0000000000..468e97da9f --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountsAdapter.kt @@ -0,0 +1,36 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates + +import android.view.ViewGroup +import coil.ImageLoader +import io.novafoundation.nova.common.list.GroupedListHolder +import io.novafoundation.nova.common.utils.inflateChild +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountDiffCallback +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountGroupViewHolderFactory +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.CommonAccountsAdapter +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountTitleHolder +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountTitleGroupRvItem +import io.novafoundation.nova.feature_account_impl.R + +class DelegatedAccountsAdapter( + private val imageLoader: ImageLoader +) : CommonAccountsAdapter( + accountItemHandler = null, + imageLoader = imageLoader, + diffCallback = AccountDiffCallback(AccountTitleGroupRvItem::class.java), + groupFactory = DelegatedAccountsGroupFactory(), + groupBinder = { holder, item -> (holder as AccountTitleHolder).bind(item) }, + initialMode = AccountHolder.Mode.VIEW +) + +private class DelegatedAccountsGroupFactory : AccountGroupViewHolderFactory { + + override fun create(parent: ViewGroup): GroupedListHolder { + return AccountTitleHolder( + parent.inflateChild( + R.layout.item_delegated_account_group, + false + ) + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesComponent.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesComponent.kt new file mode 100644 index 0000000000..042959dbcf --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesComponent.kt @@ -0,0 +1,26 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates.di + +import androidx.fragment.app.Fragment +import dagger.BindsInstance +import dagger.Subcomponent +import io.novafoundation.nova.common.di.scope.ScreenScope +import io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates.DelegatedAccountUpdatesBottomSheet + +@Subcomponent( + modules = [ + DelegatedAccountUpdatesModule::class + ] +) +@ScreenScope +interface DelegatedAccountUpdatesComponent { + + @Subcomponent.Factory + interface Factory { + + fun create( + @BindsInstance fragment: Fragment + ): DelegatedAccountUpdatesComponent + } + + fun inject(bottomSheet: DelegatedAccountUpdatesBottomSheet) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesModule.kt new file mode 100644 index 0000000000..026dab0742 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesModule.kt @@ -0,0 +1,41 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates.di + +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoMap +import io.novafoundation.nova.common.data.network.AppLinksProvider +import io.novafoundation.nova.common.di.viewmodel.ViewModelKey +import io.novafoundation.nova.common.di.viewmodel.ViewModelModule +import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter +import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates.DelegatedAccountUpdatesViewModel + +@Module(includes = [ViewModelModule::class]) +class DelegatedAccountUpdatesModule { + + @Provides + @IntoMap + @ViewModelKey(DelegatedAccountUpdatesViewModel::class) + fun provideViewModel( + delegatedMetaAccountUpdatesListingMixinFactory: DelegatedMetaAccountUpdatesListingMixinFactory, + accountRouter: AccountRouter, + appLinksProvider: AppLinksProvider, + ): ViewModel { + return DelegatedAccountUpdatesViewModel( + delegatedMetaAccountUpdatesListingMixinFactory, + accountRouter, + appLinksProvider + ) + } + + @Provides + fun provideViewModelCreator( + fragment: Fragment, + viewModelFactory: ViewModelProvider.Factory + ): DelegatedAccountUpdatesViewModel { + return ViewModelProvider(fragment, viewModelFactory).get(DelegatedAccountUpdatesViewModel::class.java) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/SelectAddressViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/SelectAddressViewModel.kt index f7f779b74a..8405dcfe04 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/SelectAddressViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/SelectAddressViewModel.kt @@ -1,8 +1,8 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.list.selectAddress import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressForTransactionRequester import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressForTransactionResponder import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter @@ -20,7 +20,7 @@ class SelectAddressViewModel( override val walletsListingMixin = accountListingMixinFactory.create(this, request.fromChainId, request.destinationChainId, request.selectedAddress) - override val mode: AccountsAdapter.Mode = AccountsAdapter.Mode.SWITCH + override val mode: AccountHolder.Mode = AccountHolder.Mode.SWITCH override fun accountClicked(accountModel: AccountUi) { launch { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletFragment.kt index ce3b5ca44d..12e9fc7301 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletFragment.kt @@ -24,4 +24,9 @@ class SwitchWalletFragment : WalletListFragment() { .create(this) .inject(this) } + + override fun onDestroy() { + super.onDestroy() + viewModel.onDestroy() + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletViewModel.kt index 69a2b65772..7513a53d54 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletViewModel.kt @@ -1,8 +1,9 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.list.switching +import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountWithBalanceListingMixinFactory import io.novafoundation.nova.feature_account_impl.presentation.account.list.WalletListViewModel @@ -11,12 +12,19 @@ import kotlinx.coroutines.launch class SwitchWalletViewModel( private val accountInteractor: AccountInteractor, private val router: AccountRouter, + private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, accountListingMixinFactory: MetaAccountWithBalanceListingMixinFactory, ) : WalletListViewModel() { override val walletsListingMixin = accountListingMixinFactory.create(this) - override val mode: AccountsAdapter.Mode = AccountsAdapter.Mode.SWITCH + override val mode: AccountHolder.Mode = AccountHolder.Mode.SWITCH + + init { + if (metaAccountsUpdatesRegistry.hasUpdates()) { + router.openDelegatedAccountsUpdates() + } + } override fun accountClicked(accountModel: AccountUi) { launch { @@ -29,4 +37,8 @@ class SwitchWalletViewModel( fun settingsClicked() { router.openWallets() } + + fun onDestroy() { + metaAccountsUpdatesRegistry.clear() + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/di/SwitchWalletModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/di/SwitchWalletModule.kt index 250ac8d7a8..1f5b197596 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/di/SwitchWalletModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/di/SwitchWalletModule.kt @@ -8,6 +8,7 @@ import dagger.Provides import dagger.multibindings.IntoMap import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule +import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountWithBalanceListingMixinFactory @@ -23,11 +24,13 @@ class SwitchWalletModule { accountInteractor: AccountInteractor, router: AccountRouter, accountListingMixinFactory: MetaAccountWithBalanceListingMixinFactory, + metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry ): ViewModel { return SwitchWalletViewModel( accountInteractor = accountInteractor, router = router, accountListingMixinFactory = accountListingMixinFactory, + metaAccountsUpdatesRegistry = metaAccountsUpdatesRegistry ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt index 3188de877c..d740726cd9 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt @@ -8,8 +8,9 @@ import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.view.dialog.warningDialog import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent import javax.inject.Inject @@ -17,7 +18,7 @@ import kotlinx.android.synthetic.main.fragment_accounts.accountListToolbar import kotlinx.android.synthetic.main.fragment_accounts.accountsList import kotlinx.android.synthetic.main.fragment_accounts.addAccount -class WalletManagmentFragment : BaseFragment(), AccountsAdapter.AccountItemHandler { +class WalletManagmentFragment : BaseFragment(), AccountHolder.AccountItemHandler { @Inject lateinit var imageLoader: ImageLoader diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt index 5eabd353f0..b05d7ffab8 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt @@ -7,8 +7,8 @@ import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingOrDenyingAc import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.presenatation.account.add.AddAccountPayload -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter.Mode +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder.Mode +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountWithBalanceListingMixinFactory @@ -21,15 +21,15 @@ class WalletManagmentViewModel( private val accountRouter: AccountRouter, private val resourceManager: ResourceManager, private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, - private val accountListingMixinFactory: MetaAccountWithBalanceListingMixinFactory, + private val accountListingMixinFactory: MetaAccountWithBalanceListingMixinFactory ) : BaseViewModel() { val walletsListingMixin = accountListingMixinFactory.create(this) - val mode = MutableStateFlow(Mode.VIEW) + val mode = MutableStateFlow(Mode.OPEN) val toolbarAction = mode.map { - if (it == Mode.VIEW) { + if (it == Mode.OPEN) { resourceManager.getString(R.string.common_edit) } else { resourceManager.getString(R.string.common_done) @@ -44,7 +44,7 @@ class WalletManagmentViewModel( } fun editClicked() { - val newMode = if (mode.value == Mode.VIEW) Mode.EDIT else Mode.VIEW + val newMode = if (mode.value == Mode.OPEN) Mode.EDIT else Mode.OPEN mode.value = newMode } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/di/WalletManagmentModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/di/WalletManagmentModule.kt index 00ec5289fc..02f75fc60a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/di/WalletManagmentModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/di/WalletManagmentModule.kt @@ -28,7 +28,13 @@ class WalletManagmentModule { actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, metaAccountListingMixinFactory: MetaAccountWithBalanceListingMixinFactory ): ViewModel { - return WalletManagmentViewModel(interactor, router, resourceManager, actionAwaitableMixinFactory, metaAccountListingMixinFactory) + return WalletManagmentViewModel( + interactor, + router, + resourceManager, + actionAwaitableMixinFactory, + metaAccountListingMixinFactory + ) } @Provides diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/mixin/selectWallet/SelectWalletViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/mixin/selectWallet/SelectWalletViewModel.kt index 85b0f312e9..d935a4fb6c 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/mixin/selectWallet/SelectWalletViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/mixin/selectWallet/SelectWalletViewModel.kt @@ -1,8 +1,8 @@ package io.novafoundation.nova.feature_account_impl.presentation.mixin.selectWallet import io.novafoundation.nova.common.navigation.requireLastInput -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletResponder import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter @@ -22,7 +22,7 @@ class SelectWalletViewModel( isMetaAccountSelected = { currentSelectedId == it.id } ) - override val mode: AccountsAdapter.Mode = AccountsAdapter.Mode.SWITCH + override val mode: AccountHolder.Mode = AccountHolder.Mode.SWITCH override fun accountClicked(accountModel: AccountUi) { responder.respond(SelectWalletCommunicator.Response(accountModel.id)) diff --git a/feature-account-impl/src/main/res/layout/bottom_sheet_delegated_account_updates.xml b/feature-account-impl/src/main/res/layout/bottom_sheet_delegated_account_updates.xml new file mode 100644 index 0000000000..dee585a82b --- /dev/null +++ b/feature-account-impl/src/main/res/layout/bottom_sheet_delegated_account_updates.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt index 565674201e..f6863975bd 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt @@ -9,8 +9,9 @@ import androidx.recyclerview.widget.ConcatAdapter import coil.ImageLoader import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.utils.applyStatusBarInsets -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountsAdapter +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder import io.novafoundation.nova.feature_ledger_impl.R import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.LedgerMessagePresentable import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.bottomSheet.setupLedgerMessages @@ -21,7 +22,7 @@ import javax.inject.Inject abstract class SelectAddressLedgerFragment : BaseFragment(), - AccountsAdapter.AccountItemHandler, + AccountHolder.AccountItemHandler, LedgerSelectAddressLoadMoreAdapter.Handler { companion object { @@ -34,7 +35,7 @@ abstract class SelectAddressLedgerFragment : @Inject protected lateinit var imageLoader: ImageLoader - private val addressesAdapter by lazy(LazyThreadSafetyMode.NONE) { AccountsAdapter(this, imageLoader, AccountsAdapter.Mode.VIEW) } + private val addressesAdapter by lazy(LazyThreadSafetyMode.NONE) { AccountsAdapter(this, imageLoader, AccountHolder.Mode.OPEN) } private val loadMoreAdapter = LedgerSelectAddressLoadMoreAdapter(handler = this, lifecycleOwner = this) @Inject diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt index fde3cc1a50..93f499d3f9 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt @@ -15,7 +15,7 @@ import io.novafoundation.nova.common.utils.mapList import io.novafoundation.nova.common.utils.withFlagSet import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi import io.novafoundation.nova.feature_account_api.presenatation.account.icon.createAccountAddressModel -import io.novafoundation.nova.feature_account_api.presenatation.account.listing.AccountUi +import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_ledger_impl.R import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.LedgerAccountWithBalance import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor @@ -158,7 +158,9 @@ abstract class SelectAddressLedgerViewModel( isClickable = true, picture = addressModel.image, chainIconUrl = null, - subtitleIconRes = null + subtitleIconRes = null, + enabled = true, + updateIndicator = false ) } } From cc3610e072e4b445555dcea38038252829b3272f Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 6 Dec 2023 23:14:28 +0100 Subject: [PATCH 015/100] Update MetaAccountWithBalanceListingMixin.kt --- .../common/listing/MetaAccountWithBalanceListingMixin.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt index d3b4d9a99f..2ac5858c29 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt @@ -1,21 +1,14 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.common.listing -import android.text.SpannableStringBuilder import io.novafoundation.nova.common.list.toListWithHeaders import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.WithCoroutineScopeExtensions -import io.novafoundation.nova.common.utils.appendEnd -import io.novafoundation.nova.common.utils.appendSpace -import io.novafoundation.nova.common.utils.append -import io.novafoundation.nova.common.utils.colorSpan -import io.novafoundation.nova.common.utils.drawableSpan import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountWithTotalBalance import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase -import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_currency_api.presentation.formatters.formatAsCurrency import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.map From 6b2d82730097caf589e580bad1760f279f60aa9c Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 6 Dec 2023 23:27:11 +0100 Subject: [PATCH 016/100] Update MetaAccountGroupingInteractorImpl.kt --- .../domain/MetaAccountGroupingInteractorImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt index 8ba9806218..987dad5f1a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt @@ -135,9 +135,9 @@ class MetaAccountGroupingInteractorImpl( LightMetaAccount.Type.SECRETS, LightMetaAccount.Type.POLKADOT_VAULT, LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.PROXIED, LightMetaAccount.Type.LEDGER -> true - LightMetaAccount.Type.PROXIED, LightMetaAccount.Type.WATCH_ONLY -> false } } From 360f99a7b3295eb1cc258d788051874c7ffbcd69 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 7 Dec 2023 11:21:09 +0100 Subject: [PATCH 017/100] Update MetaAccount.kt --- .../nova/feature_account_api/domain/model/MetaAccount.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt index bdc6dcf9c9..e029b6af49 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt @@ -29,13 +29,13 @@ interface LightMetaAccount { val isSelected: Boolean val name: String val type: Type - val state: LightMetaAccount.State + val status: LightMetaAccount.Status enum class Type { SECRETS, WATCH_ONLY, PARITY_SIGNER, LEDGER, POLKADOT_VAULT, PROXIED } - enum class State { + enum class Status { ACTIVE, DEACTIVATED } } @@ -50,6 +50,7 @@ fun LightMetaAccount( isSelected: Boolean, name: String, type: LightMetaAccount.Type, + status: LightMetaAccount.Status ) = object : LightMetaAccount { override val id: Long = id override val substratePublicKey: ByteArray? = substratePublicKey @@ -60,6 +61,7 @@ fun LightMetaAccount( override val isSelected: Boolean = isSelected override val name: String = name override val type: LightMetaAccount.Type = type + override val status: LightMetaAccount.Status = status } class MetaAccount( @@ -74,7 +76,7 @@ class MetaAccount( override val isSelected: Boolean, override val name: String, override val type: LightMetaAccount.Type, - override val state: LightMetaAccount.State + override val status: LightMetaAccount.Status ) : LightMetaAccount { class ChainAccount( From a4fcc25bb65da807462fd7fb0b70e1d67fdbda35 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 7 Dec 2023 11:22:15 +0100 Subject: [PATCH 018/100] Added sort for delegated accounts --- .../MetaAccountGroupingInteractor.kt | 2 +- .../data/mappers/Mappers.kt | 11 ++++++----- .../MetaAccountGroupingInteractorImpl.kt | 18 +++++++++++++----- .../DelegatedMetaAccountUpdatesListingMixin.kt | 10 +++++----- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/MetaAccountGroupingInteractor.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/MetaAccountGroupingInteractor.kt index 4319d7c9dd..9ee001d004 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/MetaAccountGroupingInteractor.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/MetaAccountGroupingInteractor.kt @@ -16,7 +16,7 @@ interface MetaAccountGroupingInteractor { fun getMetaAccountsForTransaction(fromId: ChainId, destinationId: ChainId): Flow> - fun updatedProxieds(): Flow> + fun updatedProxieds(): Flow> suspend fun hasAvailableMetaAccountsForDestination(fromId: ChainId, destinationId: ChainId): Boolean } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index fff6ad0d2f..1fcf22020f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -150,7 +150,7 @@ fun mapMetaAccountLocalToMetaAccount( isSelected = isSelected, name = name, type = mapMetaAccountTypeFromLocal(type), - state = mapMetaAccountStateFromLocal(status) + status = mapMetaAccountStateFromLocal(status) ) } } @@ -168,7 +168,8 @@ fun mapMetaAccountLocalToLightMetaAccount( ethereumPublicKey = ethereumPublicKey, isSelected = isSelected, name = name, - type = mapMetaAccountTypeFromLocal(type) + type = mapMetaAccountTypeFromLocal(type), + status = mapMetaAccountStateFromLocal(status) ) } } @@ -207,9 +208,9 @@ private fun mapProxyTypeToString(proxyType: String): ProxyAccount.ProxyType { } } -private fun mapMetaAccountStateFromLocal(local: MetaAccountLocal.Status): LightMetaAccount.State { +private fun mapMetaAccountStateFromLocal(local: MetaAccountLocal.Status): LightMetaAccount.Status { return when (local) { - MetaAccountLocal.Status.ACTIVE -> LightMetaAccount.State.ACTIVE - MetaAccountLocal.Status.DEACTIVATED -> LightMetaAccount.State.DEACTIVATED + MetaAccountLocal.Status.ACTIVE -> LightMetaAccount.Status.ACTIVE + MetaAccountLocal.Status.DEACTIVATED -> LightMetaAccount.Status.DEACTIVATED } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt index 987dad5f1a..55b3f9628e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt @@ -72,15 +72,15 @@ class MetaAccountGroupingInteractorImpl( .toSortedMap(metaAccountTypeComparator()) } - override fun updatedProxieds(): Flow> { + override fun updatedProxieds(): Flow> { return combine( metaAccountsUpdatesRegistry.observeUpdates(), accountRepository.allMetaAccountsFlow(), chainRegistry.chainsById - ) { metaIds, metaAccount, chainsById -> + ) { updatedMetaIds, metaAccount, chainsById -> val metaById = metaAccount.associateBy(MetaAccount::id) metaAccount - .filter { it.type == LightMetaAccount.Type.PROXIED && metaIds.contains(it.id) } + .filter { it.type == LightMetaAccount.Type.PROXIED && updatedMetaIds.contains(it.id) } .map { ProxiedAndProxyMetaAccount( it, @@ -88,8 +88,9 @@ class MetaAccountGroupingInteractorImpl( chainsById[it.proxy?.chainId] ?: error("Proxy chain not found") ) } - .groupBy { it.proxied.state } - }.catch { emit(emptyMap()) } + .groupBy { it.proxied.status } + .toSortedMap(metaAccountStateComparator()) + }.catch { emit(sortedMapOf()) } } override suspend fun hasAvailableMetaAccountsForDestination(fromId: ChainId, destinationId: ChainId): Boolean { @@ -153,4 +154,11 @@ class MetaAccountGroupingInteractorImpl( LightMetaAccount.Type.WATCH_ONLY -> 5 } } + + private fun metaAccountStateComparator() = compareBy { + when (it) { + LightMetaAccount.Status.ACTIVE -> 0 + LightMetaAccount.Status.DEACTIVATED -> 1 + } + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt index 9e64fae939..1f7dd35fe4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt @@ -49,10 +49,10 @@ private class DelegatedMetaAccountUpdatesListingMixin( } .shareInBackground() - private fun mapHeader(state: LightMetaAccount.State): AccountTitleGroupRvItem { - val text = when (state) { - LightMetaAccount.State.ACTIVE -> resourceManager.getString(R.string.account_proxieds) - LightMetaAccount.State.DEACTIVATED -> resourceManager.getString(R.string.proxieds_updates_deactivated_title) + private fun mapHeader(status: LightMetaAccount.Status): AccountTitleGroupRvItem { + val text = when (status) { + LightMetaAccount.Status.ACTIVE -> resourceManager.getString(R.string.account_proxieds) + LightMetaAccount.Status.DEACTIVATED -> resourceManager.getString(R.string.proxieds_updates_deactivated_title) } return AccountTitleGroupRvItem(text) @@ -68,7 +68,7 @@ private class DelegatedMetaAccountUpdatesListingMixin( picture = walletUiUseCase.walletIcon(proxied), chainIconUrl = proxiedWithProxy.chain.icon, subtitleIconRes = null, - enabled = proxiedWithProxy.proxied.state == LightMetaAccount.State.ACTIVE, + enabled = proxiedWithProxy.proxied.status == LightMetaAccount.Status.ACTIVE, updateIndicator = false ) } From 327be58ae548460d1c99561bebaf576a07c78bc7 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Sun, 10 Dec 2023 20:41:27 +0100 Subject: [PATCH 019/100] Increase susbtrate library version --- build.gradle | 2 +- .../nova/common/utils/FearlessLibExt.kt | 11 +++++++++++ .../ethereum/transaction/RealEvmTransactionService.kt | 10 ++-------- .../data/signer/SeparateFlowSigner.kt | 8 ++++++-- .../data/signer/ledger/LedgerSigner.kt | 4 ++-- .../data/signer/paritySigner/PolkadotVaultSigner.kt | 7 ++++--- .../data/signer/secrets/SecretsSigner.kt | 7 ++++--- .../data/signer/watchOnly/WatchOnlySigner.kt | 7 ++++--- .../feature_external_sign_impl/data/evmApi/EvmApi.kt | 10 ++-------- .../nova/runtime/extrinsic/FeeSigner.kt | 7 ++++--- 10 files changed, 40 insertions(+), 33 deletions(-) diff --git a/build.gradle b/build.gradle index bb67be0474..a4b076ce2f 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,7 @@ buildscript { web3jVersion = '4.9.5' - fearlessLibVersion = '1.9.1' + fearlessLibVersion = '1.11.0' gifVersion = '1.2.19' diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index ea6ad0c683..3a36315811 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -53,6 +53,8 @@ import java.io.ByteArrayOutputStream import java.math.BigInteger import java.nio.ByteBuffer import java.nio.ByteOrder +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw +import org.web3j.crypto.Sign typealias PalletName = String @@ -279,6 +281,15 @@ fun GenericEvent.Instance.instanceOf(moduleName: String, eventName: String): Boo fun structOf(vararg pairs: Pair) = Struct.Instance(mapOf(*pairs)) +fun SignedRaw.toEcdsaSignatureData(): Sign.SignatureData { + return signatureWrapper.run { + require(this is SignatureWrapper.Ecdsa) + Sign.SignatureData(v, r, s) + } +} + +fun SignedRaw.asHexString() = signatureWrapper.asHexString() + fun SignatureWrapper.asHexString() = signature.toHexString(withPrefix = true) fun String.ethereumAddressToAccountId() = asEthereumAddress().toAccountId().value diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index fcfc89cdce..de79f844e2 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_impl.data.ethereum.transaction import io.novafoundation.nova.common.utils.castOrNull +import io.novafoundation.nova.common.utils.toEcdsaSignatureData import io.novafoundation.nova.core.ethereum.Web3Api import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionBuilding import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService @@ -21,7 +22,6 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.awaitCallEthereumApiOrThrow import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw import org.web3j.crypto.RawTransaction @@ -99,7 +99,7 @@ internal class RealEvmTransactionService( val accountId = metaAccount.requireAccountIdIn(chain) val signerPayload = SignerPayloadRaw(encodedTx, accountId) - val signatureData = signer.signRaw(signerPayload).toSignatureData() + val signatureData = signer.signRaw(signerPayload).toEcdsaSignatureData() val eip155SignatureData: Sign.SignatureData = TransactionEncoder.createEip155SignatureData(signatureData, ethereumChainId) @@ -124,12 +124,6 @@ internal class RealEvmTransactionService( default } - private fun SignatureWrapper.toSignatureData(): Sign.SignatureData { - require(this is SignatureWrapper.Ecdsa) - - return Sign.SignatureData(v, r, s) - } - private fun RawTransaction.encodeWith(signatureData: Sign.SignatureData): ByteArray { val values = TransactionEncoder.asRlpValues(this, signatureData) val rlpList = RlpList(values) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt index a93e1b29b0..83df5b4a9e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterSc import io.novafoundation.nova.feature_account_api.presenatation.sign.SignatureWrapper import io.novafoundation.nova.feature_account_api.presenatation.sign.awaitConfirmation import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import kotlinx.coroutines.Dispatchers @@ -17,7 +18,7 @@ abstract class SeparateFlowSigner( private val signFlowRequester: SignInterScreenRequester, ) : Signer { - override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignatureWrapper { + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { signingSharedState.set(payloadExtrinsic) val result = withContext(Dispatchers.Main) { @@ -29,7 +30,10 @@ abstract class SeparateFlowSigner( } if (result is SignInterScreenCommunicator.Response.Signed) { - return SignatureWrapper(result.signature) + return SignedExtrinsic( + payloadExtrinsic, + SignatureWrapper(result.signature) + ) } else { throw SigningCancelledException() } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt index 48e3b60ce1..ba5c05dbd4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt @@ -7,7 +7,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterSc import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.data.signer.SeparateFlowSigner import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw @@ -18,7 +18,7 @@ class LedgerSigner( private val messageSigningNotSupported: SigningNotSupportedPresentable ) : SeparateFlowSigner(signingSharedState, signFlowRequester) { - override suspend fun signRaw(payload: SignerPayloadRaw): SignatureWrapper { + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { messageSigningNotSupported.presentSigningNotSupported( SigningNotSupportedPresentable.Payload( iconRes = R.drawable.ic_ledger, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt index e44f2af127..db0961074c 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt @@ -9,7 +9,8 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.polkadot import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.data.signer.SeparateFlowSigner import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw @@ -22,13 +23,13 @@ abstract class PolkadotVaultVariantSigner( private val messageSigningNotSupported: SigningNotSupportedPresentable ) : SeparateFlowSigner(signingSharedState, signFlowRequester) { - override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignatureWrapper { + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { signFlowRequester.setUsedVariant(variant) return super.signExtrinsic(payloadExtrinsic) } - override suspend fun signRaw(payload: SignerPayloadRaw): SignatureWrapper { + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { val config = polkadotVaultVariantConfigProvider.variantConfigFor(variant) messageSigningNotSupported.presentSigningNotSupported( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/secrets/SecretsSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/secrets/SecretsSigner.kt index ec29940ac6..200bc01919 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/secrets/SecretsSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/secrets/SecretsSigner.kt @@ -11,9 +11,10 @@ import io.novafoundation.nova.feature_account_api.domain.model.multiChainEncrypt import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chainsById import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.KeyPairSigner +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw @@ -36,14 +37,14 @@ class SecretsSigner( private val twoFactorVerificationService: TwoFactorVerificationService ) : Signer { - override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignatureWrapper { + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { runTwoFactorVerificationIfEnabled() val delegate = createDelegate(payloadExtrinsic.accountId) return delegate.signExtrinsic(payloadExtrinsic) } - override suspend fun signRaw(payload: SignerPayloadRaw): SignatureWrapper { + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { runTwoFactorVerificationIfEnabled() val delegate = createDelegate(payload.accountId) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/watchOnly/WatchOnlySigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/watchOnly/WatchOnlySigner.kt index 512b7b4669..1d98b254cc 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/watchOnly/WatchOnlySigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/watchOnly/WatchOnlySigner.kt @@ -2,7 +2,8 @@ package io.novafoundation.nova.feature_account_impl.data.signer.watchOnly import io.novafoundation.nova.common.base.errors.SigningCancelledException import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw @@ -11,11 +12,11 @@ class WatchOnlySigner( private val watchOnlySigningPresenter: WatchOnlyMissingKeysPresenter ) : Signer { - override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignatureWrapper { + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { cannotSign() } - override suspend fun signRaw(payload: SignerPayloadRaw): SignatureWrapper { + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { cannotSign() } diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/data/evmApi/EvmApi.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/data/evmApi/EvmApi.kt index 0c4109c18f..8a04d5fe09 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/data/evmApi/EvmApi.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/data/evmApi/EvmApi.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_external_sign_impl.data.evmApi +import io.novafoundation.nova.common.utils.toEcdsaSignatureData import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProvider import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.feature_external_sign_api.model.signPayload.evm.EvmChainSource @@ -7,7 +8,6 @@ import io.novafoundation.nova.feature_external_sign_api.model.signPayload.evm.Ev import io.novafoundation.nova.runtime.ethereum.sendSuspend import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.findEvmCallApi -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer @@ -166,7 +166,7 @@ private class Web3JEvmApi( ): String { val encodedTx = TransactionEncoder.encode(transaction, ethereumChainId) val signerPayload = SignerPayloadRaw(encodedTx, accountId) - val signatureData = signer.signRaw(signerPayload).toSignatureData() + val signatureData = signer.signRaw(signerPayload).toEcdsaSignatureData() val eip155SignatureData: SignatureData = TransactionEncoder.createEip155SignatureData(signatureData, ethereumChainId) @@ -195,12 +195,6 @@ private class Web3JEvmApi( return web3.ethEstimateGas(tx).sendSuspend().amountUsed } - private fun SignatureWrapper.toSignatureData(): SignatureData { - require(this is SignatureWrapper.Ecdsa) - - return SignatureData(v, r, s) - } - private fun RawTransaction.encodeWith(signatureData: SignatureData): ByteArray { val values = TransactionEncoder.asRlpValues(this, signatureData) val rlpList = RlpList(values) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt index bbda80aad7..20904ea7f1 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt @@ -4,11 +4,12 @@ import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.encrypt.EncryptionType import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.encrypt.keypair.Keypair import jp.co.soramitsu.fearless_utils.encrypt.keypair.ethereum.EthereumKeypairFactory import jp.co.soramitsu.fearless_utils.encrypt.keypair.substrate.SubstrateKeypairFactory import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.KeyPairSigner +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw @@ -19,13 +20,13 @@ class FeeSigner(private val chain: Chain) : Signer { private val keypair = generateFakeKeyPair() - override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignatureWrapper { + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { val signer = KeyPairSigner(keypair, multiChainEncryption()) return signer.signExtrinsic(payloadExtrinsic) } - override suspend fun signRaw(payload: SignerPayloadRaw): SignatureWrapper { + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { val signer = KeyPairSigner(keypair, multiChainEncryption()) return signer.signRaw(payload) From 606fbac618998b9ec82db5e4c5177baa87ace0b4 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Sun, 10 Dec 2023 22:50:46 +0100 Subject: [PATCH 020/100] Fixed pr notes --- .../data/proxy/RealProxySyncService.kt | 26 ++++++++++--------- .../data/repository/RealProxyRepository.kt | 24 ++++++++++------- .../AddAccountLauncherProvider.kt | 4 +-- .../source/query/BaseStorageQueryContext.kt | 12 ++++----- .../source/query/StorageQueryContext.kt | 4 +-- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 4997369e93..f82e6d6e0b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -36,23 +36,25 @@ class RealProxySyncService( override suspend fun startSyncing() { if (!accounRepository.hasMetaAccounts()) return - val metaAccounts = getMetaAccounts() + runCatching { + val metaAccounts = getMetaAccounts() - val supportedProxyChains = getSupportedProxyChains() - val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } + val supportedProxyChains = getSupportedProxyChains() + val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } - // proxiedsWithProxies will be usefull when we union differen proxy types to one account - val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> - proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) - } + // proxiedsWithProxies will be usefull when we union differen proxy types to one account + val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> + proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) + } - val newProxies = proxiedsWithProxies.formatToLocalProxies() - val oldProxies = accountDao.getAllProxyAccounts() + val newProxies = proxiedsWithProxies.formatToLocalProxies() + val oldProxies = accountDao.getAllProxyAccounts() - val proxiesDiff = CollectionDiffer.findDiff(newProxies, oldProxies, forceUseNewItems = false) + val proxiesDiff = CollectionDiffer.findDiff(newProxies, oldProxies, forceUseNewItems = false) - insertMetaAndChainAccounts(proxiesDiff) - insertProxies(proxiesDiff) + insertMetaAndChainAccounts(proxiesDiff) + insertProxies(proxiesDiff) + } } private suspend fun insertMetaAndChainAccounts(proxiesDiff: CollectionDiffer.Diff) { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index 2f8a002cc3..d4eb45b01b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -23,7 +23,7 @@ class RealProxyRepository( override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List { val delegatorToProxies = receiveAllProxies(chainId) - val accountIdToMetaAccounts = metaAccountIds.associateBy { it.accountId.intoKey() } + val accountIdToMetaAccounts = metaAccountIds.groupBy { it.accountId.intoKey() } return delegatorToProxies .mapNotNull { (delegator, proxies) -> @@ -46,7 +46,9 @@ class RealProxyRepository( binding = { result, _ -> bindProxyAccounts(result) }, - onDecodeException = { } + recover = { _, _ -> + // Do nothing if entry binding throws an exception + } ) } } @@ -77,16 +79,18 @@ class RealProxyRepository( private fun matchProxiesToAccountsAndMap( proxies: Map, - accountIdToMetaAccounts: Map + accountIdToMetaAccounts: Map> ): List { - return proxies.mapNotNull { (proxyAccountId, proxyType) -> - val matchedAccount = accountIdToMetaAccounts[proxyAccountId] ?: return@mapNotNull null + return proxies.flatMap { (proxyAccountId, proxyType) -> + val matchedAccounts = accountIdToMetaAccounts[proxyAccountId] ?: return@flatMap emptyList() - ProxiedWithProxies.Proxy( - accountId = proxyAccountId.value, - metaId = matchedAccount.metaId, - proxyType = proxyType - ) + matchedAccounts.map { + ProxiedWithProxies.Proxy( + accountId = proxyAccountId.value, + metaId = it.metaId, + proxyType = proxyType + ) + } } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/mixin/addAccountChooser/AddAccountLauncherProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/mixin/addAccountChooser/AddAccountLauncherProvider.kt index e2843f8a4e..1fd646b8c4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/mixin/addAccountChooser/AddAccountLauncherProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/mixin/addAccountChooser/AddAccountLauncherProvider.kt @@ -44,9 +44,9 @@ class AddAccountLauncherProvider( when (metaAccount.type) { LightMetaAccount.Type.SECRETS -> launchAddFromSecrets(chain, metaAccount) LightMetaAccount.Type.WATCH_ONLY -> launchAddWatchOnly(chain, metaAccount) - // adding chain accounts is not supported for Polkadot Vault like wallets - LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.POLKADOT_VAULT, LightMetaAccount.Type.PROXIED -> {} LightMetaAccount.Type.LEDGER -> launchAddLedger(chain, metaAccount) + // adding chain accounts is not supported for Polkadot Vault like wallets and for Proxied wallets + LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.POLKADOT_VAULT, LightMetaAccount.Type.PROXIED -> {} } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt index 734e02a9bd..d809be9705 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt @@ -62,7 +62,7 @@ abstract class BaseStorageQueryContext( vararg prefixArgs: Any?, keyExtractor: (StorageKeyComponents) -> K, binding: DynamicInstanceBinderWithKey, - onDecodeException: (Exception) -> Unit + recover: (exception: Exception, rawValue: String?) -> Unit ): Map { val prefix = storageKey(runtime, *prefixArgs) @@ -73,7 +73,7 @@ abstract class BaseStorageQueryContext( storageEntry = this, keyExtractor = keyExtractor, binding = binding, - onDecodeException = onDecodeException + recover = recover ) } @@ -81,7 +81,7 @@ abstract class BaseStorageQueryContext( keysArguments: List>, keyExtractor: (StorageKeyComponents) -> K, binding: DynamicInstanceBinderWithKey, - onDecodeException: (Exception) -> Unit + recover: (exception: Exception, rawValue: String?) -> Unit ): Map { val entries = queryKeys(storageKeys(runtime, keysArguments), at) @@ -90,7 +90,7 @@ abstract class BaseStorageQueryContext( storageEntry = this, keyExtractor = keyExtractor, binding = binding, - onDecodeException = onDecodeException + recover = recover ) } @@ -242,7 +242,7 @@ abstract class BaseStorageQueryContext( storageEntry: StorageEntry, keyExtractor: (StorageKeyComponents) -> K, binding: DynamicInstanceBinderWithKey, - onDecodeException: (Exception) -> Unit = { throw it } + recover: (exception: Exception, rawValue: String?) -> Unit = { exception, _ -> throw exception } ): Map { val returnType = storageEntry.type.value ?: incompatible() @@ -255,7 +255,7 @@ abstract class BaseStorageQueryContext( val decoded = value?.let { returnType.fromHexOrIncompatible(value, runtime) } binding(decoded, key) } catch (e: Exception) { - onDecodeException(e) + recover(e, value) null } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt index 8572d1a8ec..383688a6e8 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt @@ -50,7 +50,7 @@ interface StorageQueryContext { vararg prefixArgs: Any?, keyExtractor: (StorageKeyComponents) -> K, binding: DynamicInstanceBinderWithKey, - onDecodeException: (Exception) -> Unit = { throw it } + recover: (exception: Exception, rawValue: String?) -> Unit = { exception, _ -> throw exception } ): Map suspend fun StorageEntry.entriesRaw( @@ -65,7 +65,7 @@ interface StorageQueryContext { keysArguments: List>, keyExtractor: (StorageKeyComponents) -> K, binding: DynamicInstanceBinderWithKey, - onDecodeException: (Exception) -> Unit = { throw it } + recover: (exception: Exception, rawValue: String?) -> Unit = { exception, _ -> throw exception } ): Map suspend fun StorageEntry.query( From bb1140f1d2034932ba1068c48ed7dd3b69513b17 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Sun, 10 Dec 2023 22:52:22 +0100 Subject: [PATCH 021/100] Fixed pr notes --- .../presenatation/account/listing/holders/AccountHolder.kt | 4 ++-- .../account/management/WalletManagmentViewModel.kt | 6 +++--- .../common/selectAddress/SelectAddressLedgerFragment.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt index eec79bba6b..65f0abcbe8 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt @@ -32,7 +32,7 @@ class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedL } enum class Mode { - VIEW, OPEN, EDIT, SWITCH + VIEW, SELECT, EDIT, SWITCH } init { @@ -92,7 +92,7 @@ class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedL setOnClickListener(null) } - Mode.OPEN -> { + Mode.SELECT -> { itemAccountArrow.visibility = View.VISIBLE itemAccountDelete.visibility = View.GONE diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt index b05d7ffab8..52854995d3 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt @@ -26,10 +26,10 @@ class WalletManagmentViewModel( val walletsListingMixin = accountListingMixinFactory.create(this) - val mode = MutableStateFlow(Mode.OPEN) + val mode = MutableStateFlow(Mode.SELECT) val toolbarAction = mode.map { - if (it == Mode.OPEN) { + if (it == Mode.SELECT) { resourceManager.getString(R.string.common_edit) } else { resourceManager.getString(R.string.common_done) @@ -44,7 +44,7 @@ class WalletManagmentViewModel( } fun editClicked() { - val newMode = if (mode.value == Mode.OPEN) Mode.EDIT else Mode.OPEN + val newMode = if (mode.value == Mode.SELECT) Mode.EDIT else Mode.SELECT mode.value = newMode } diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt index f6863975bd..7c3dabab74 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt @@ -35,7 +35,7 @@ abstract class SelectAddressLedgerFragment : @Inject protected lateinit var imageLoader: ImageLoader - private val addressesAdapter by lazy(LazyThreadSafetyMode.NONE) { AccountsAdapter(this, imageLoader, AccountHolder.Mode.OPEN) } + private val addressesAdapter by lazy(LazyThreadSafetyMode.NONE) { AccountsAdapter(this, imageLoader, AccountHolder.Mode.SELECT) } private val loadMoreAdapter = LedgerSelectAddressLoadMoreAdapter(handler = this, lifecycleOwner = this) @Inject From 917b81c3849be97f9bd9ac09e0c59183d3af242e Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Sun, 10 Dec 2023 22:59:11 +0100 Subject: [PATCH 022/100] Added run catching --- .../data/proxy/RealProxySyncService.kt | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index ae08df9b70..6be30dae57 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -39,42 +39,43 @@ class RealProxySyncService( runCatching { val metaAccounts = getMetaAccounts() - val supportedProxyChains = getSupportedProxyChains() - val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } + val supportedProxyChains = getSupportedProxyChains() + val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } - val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> - proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) - } + val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> + proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) + } - val oldProxies = accountDao.getAllProxyAccounts() + val oldProxies = accountDao.getAllProxyAccounts() - val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) + val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) - val identitiesByChain = notAddedProxies.loadProxiedIdentities() - val proxiedsToMetaId = notAddedProxies.map { - val identity = identitiesByChain[it.proxied.chainId]?.get(it.proxied.accountId.intoKey()) - val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> - createMetaAccount(it.proxied.chainId, it.proxy.metaId, it.proxied.accountId, identity, nextPosition) + val identitiesByChain = notAddedProxies.loadProxiedIdentities() + val proxiedsToMetaId = notAddedProxies.map { + val identity = identitiesByChain[it.proxied.chainId]?.get(it.proxied.accountId.intoKey()) + val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> + createMetaAccount(it.proxied.chainId, it.proxy.metaId, it.proxied.accountId, identity, nextPosition) + } + it to proxiedMetaId } - it to proxiedMetaId - } - val chains = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> - val proxied = proxiedWithProxy.proxied - createChainAccount(proxiedMetaId, proxied.chainId, proxied.accountId) - } + val chains = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> + val proxied = proxiedWithProxy.proxied + createChainAccount(proxiedMetaId, proxied.chainId, proxied.accountId) + } - val newProxies = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> - val proxied = proxiedWithProxy.proxied - val proxy = proxiedWithProxy.proxy - createProxyAccount(proxiedMetaId, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) - } + val newProxies = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> + val proxied = proxiedWithProxy.proxied + val proxy = proxiedWithProxy.proxy + createProxyAccount(proxiedMetaId, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) + } - val deactivatedMetaAccounts = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) + val deactivatedMetaAccounts = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) - accountDao.insertChainAccounts(chains) - accountDao.insertProxies(newProxies) - accountDao.changeAccountsStatus(deactivatedMetaAccounts, MetaAccountLocal.Status.DEACTIVATED) + accountDao.insertChainAccounts(chains) + accountDao.insertProxies(newProxies) + accountDao.changeAccountsStatus(deactivatedMetaAccounts, MetaAccountLocal.Status.DEACTIVATED) + } } override suspend fun syncForMetaAccount(metaAccount: MetaAccount) { From b82046b48449ad4192183e87bd32a7a04b26da67 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Sun, 10 Dec 2023 23:33:27 +0100 Subject: [PATCH 023/100] Add failure handling --- .../feature_account_impl/data/proxy/RealProxySyncService.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 6be30dae57..5f688dad9e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -1,7 +1,9 @@ package io.novafoundation.nova.feature_account_impl.data.proxy +import android.util.Log import io.novafoundation.nova.common.address.AccountIdKey import io.novafoundation.nova.common.address.intoKey +import io.novafoundation.nova.common.utils.LOG_TAG import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal @@ -75,6 +77,8 @@ class RealProxySyncService( accountDao.insertChainAccounts(chains) accountDao.insertProxies(newProxies) accountDao.changeAccountsStatus(deactivatedMetaAccounts, MetaAccountLocal.Status.DEACTIVATED) + }.onFailure { + Log.e(LOG_TAG, "Failed to sync proxy delegators", it) } } From 8a30c8824468316333b917918eaa9143d1eb3626 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Sun, 10 Dec 2023 23:39:55 +0100 Subject: [PATCH 024/100] Add run catching --- .../data/proxy/RealProxySyncService.kt | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 076ce7bdf3..1a00d29e0a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -41,45 +41,46 @@ class RealProxySyncService( val metaAccounts = getMetaAccounts() if (metaAccounts.isEmpty()) return - val supportedProxyChains = getSupportedProxyChains() - val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } + runCatching { + val supportedProxyChains = getSupportedProxyChains() + val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } - val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> - proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) - } + val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> + proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) + } - val oldProxies = accountDao.getAllProxyAccounts() + val oldProxies = accountDao.getAllProxyAccounts() - val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) + val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) - val identitiesByChain = notAddedProxies.loadProxiedIdentities() - val proxiedsToMetaId = notAddedProxies.map { - val identity = identitiesByChain[it.proxied.chainId]?.get(it.proxied.accountId.intoKey()) - val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> - createMetaAccount(it.proxied.chainId, it.proxy.metaId, it.proxied.accountId, identity, nextPosition) + val identitiesByChain = notAddedProxies.loadProxiedIdentities() + val proxiedsToMetaId = notAddedProxies.map { + val identity = identitiesByChain[it.proxied.chainId]?.get(it.proxied.accountId.intoKey()) + val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> + createMetaAccount(it.proxied.chainId, it.proxy.metaId, it.proxied.accountId, identity, nextPosition) + } + it to proxiedMetaId } - it to proxiedMetaId - } - val chains = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> - val proxied = proxiedWithProxy.proxied - createChainAccount(proxiedMetaId, proxied.chainId, proxied.accountId) - } + val chains = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> + val proxied = proxiedWithProxy.proxied + createChainAccount(proxiedMetaId, proxied.chainId, proxied.accountId) + } - val newProxies = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> - val proxied = proxiedWithProxy.proxied - val proxy = proxiedWithProxy.proxy - createProxyAccount(proxiedMetaId, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) - } + val newProxies = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> + val proxied = proxiedWithProxy.proxied + val proxy = proxiedWithProxy.proxy + createProxyAccount(proxiedMetaId, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) + } - val deactivatedMetaAccountIds = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) + val deactivatedMetaAccountIds = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) accountDao.insertChainAccounts(chains) accountDao.insertProxies(newProxies) accountDao.changeAccountsStatus(deactivatedMetaAccountIds, MetaAccountLocal.Status.DEACTIVATED) - val changedMetaIds = proxiedsToMetaId.map { it.second } + deactivatedMetaAccountIds - metaAccountsUpdatesRegistry.addMetaIds(changedMetaIds) + val changedMetaIds = proxiedsToMetaId.map { it.second } + deactivatedMetaAccountIds + metaAccountsUpdatesRegistry.addMetaIds(changedMetaIds) }.onFailure { Log.e(LOG_TAG, "Failed to sync proxy delegators", it) } From aec3b9b2fa9736cde738dd0e348c53b11eda36b9 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 01:17:12 +0100 Subject: [PATCH 025/100] Proxied signer implementation + fee signer --- .../nova/common/utils/FearlessLibExt.kt | 8 ++ .../ActionNotAllowedBottomSheet.kt | 12 +- .../bottom_sheet_action_not_allowed.xml | 3 +- common/src/main/res/values/strings.xml | 12 ++ .../nova/core_db/dao/MetaAccountDao.kt | 6 + .../data/repository/ProxyRepository.kt | 5 + .../data/signer/SignerProvider.kt | 5 +- .../domain/interfaces/AccountRepository.kt | 5 + .../domain/model/ProxyAccount.kt | 20 ++-- .../account/proxy/ProxySigningPresenter.kt | 13 +++ .../data/extrinsic/RealExtrinsicService.kt | 14 ++- .../data/mappers/Mappers.kt | 39 ++++--- .../data/proxy/RealProxySyncService.kt | 8 +- .../data/repository/AccountRepositoryImpl.kt | 9 ++ .../data/repository/RealProxyRepository.kt | 27 ++++- .../datasource/AccountDataSource.kt | 5 + .../datasource/AccountDataSourceImpl.kt | 12 ++ .../data/signer/RealSignerProvider.kt | 25 +++- .../signer/proxy/ModuleToProxyTypeMatcher.kt | 50 ++++++++ .../data/signer/proxy/ProxiedFeeSigner.kt | 84 +++++++++++++ .../data/signer/proxy/ProxiedSigner.kt | 110 ++++++++++++++++++ .../signer/proxy/SignerPayloadModifierExt.kt | 29 +++++ .../di/AccountFeatureModule.kt | 8 +- .../di/modules/ProxySigningModule.kt | 22 ++++ .../di/modules/SignersModule.kt | 25 ++++ ...knowledgeSigningNotSupportedBottomSheet.kt | 4 +- ...ProxySignNotEnoughPermissionBottomSheet.kt | 23 ++++ ...oxySignOperationNotSupportedBottomSheet.kt | 21 ++++ .../proxy/sign/ProxySignWarningBottomSheet.kt | 49 ++++++++ .../proxy/sign/RealProxySigningPresenter.kt | 105 +++++++++++++++++ .../sign/WatchOnlySignBottomSheet.kt | 4 +- .../res/layout/bottom_sheet_proxy_warning.xml | 77 ++++++++++++ .../LedgerNotSupportedWarningBottomSheet.kt | 4 +- .../sheets/AcknowledgePhishingBottomSheet.kt | 4 +- .../PolkadotExternalSignInteractor.kt | 3 +- .../extrinsic/ExtrinsicBuilderFactory.kt | 8 +- .../DefaultFeeSigner.kt} | 7 +- .../runtime/extrinsic/feeSigner/FeeSigner.kt | 9 ++ .../extrinsic/multi/ExtrinsicSplitter.kt | 17 +-- 39 files changed, 815 insertions(+), 76 deletions(-) create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignOperationNotSupportedBottomSheet.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt create mode 100644 feature-account-impl/src/main/res/layout/bottom_sheet_proxy_warning.xml rename runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/{FeeSigner.kt => feeSigner/DefaultFeeSigner.kt} (89%) create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index 49830b9f2f..2b5d4fc83a 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -53,6 +53,7 @@ import java.io.ByteArrayOutputStream import java.math.BigInteger import java.nio.ByteBuffer import java.nio.ByteOrder +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import org.web3j.crypto.Sign @@ -301,6 +302,11 @@ fun emptyEthereumAddress() = emptyEthereumAccountId().ethereumAccountIdToAddress val SignerPayloadExtrinsic.chainId: String get() = genesisHash.toHexString() +fun CallRepresentation.toCallInstance(): CallRepresentation.Instance? { + return (this as? CallRepresentation.Instance) +} + + object Modules { const val VESTING: String = "Vesting" const val STAKING = "Staking" @@ -357,4 +363,6 @@ object Modules { const val UTILITY = "Utility" const val PROXY = "Proxy" + + const val AUCTIONS = "auctions" } diff --git a/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/ActionNotAllowedBottomSheet.kt b/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/ActionNotAllowedBottomSheet.kt index 4a5175efa2..edea3ba0e1 100644 --- a/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/ActionNotAllowedBottomSheet.kt +++ b/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/ActionNotAllowedBottomSheet.kt @@ -19,16 +19,16 @@ open class ActionNotAllowedBottomSheet( private val onSuccess: () -> Unit, ) : BaseBottomSheet(context, R.style.BottomSheetDialog), WithContextExtensions by WithContextExtensions(context) { - val image: ImageView + val iconView: ImageView get() = actionNotAllowedImage - val title: TextView + val titleView: TextView get() = actionNotAllowedTitle - val subtitle: TextView + val subtitleView: TextView get() = actionNotAllowedSubtitle - val button: PrimaryButton + val buttonView: PrimaryButton get() = actionNotAllowedOk init { @@ -50,13 +50,13 @@ open class ActionNotAllowedBottomSheet( super.setContentView(layoutResId) } - protected fun applySolidIconStyle(@DrawableRes src: Int) = with(image) { + protected fun applySolidIconStyle(@DrawableRes src: Int) = with(iconView) { setPadding(12.dp) setBackgroundResource(R.drawable.bg_icon_big) setImageResource(src) } - protected fun applyDashedIconStyle(@DrawableRes src: Int) = with(image) { + protected fun applyDashedIconStyle(@DrawableRes src: Int) = with(iconView) { setPadding(12.dp) setBackgroundResource(R.drawable.bg_icon_big_dashed) setImageResource(src) diff --git a/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml b/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml index f879107f9c..3b2a7610ca 100644 --- a/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml +++ b/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml @@ -12,11 +12,12 @@ style="@style/Widget.Nova.Puller" android:layout_marginTop="6dp" /> - + Do not show this again + + This is Delegating (Proxied) account + Transaction will be initiated by %s as a delegated account. Network fee will be paid by delegated account. + + Oops! Not enough permission + %1$s delegated %2$s only for %3$s + + Signing is not supported + Proxied wallets does not support signing arbitrary messages — only transactions + Okay, back + Delegated accounts update Nova Wallet automatically adds delegated authorities (Proxy) to a separate category for you. You can always manage wallets in Settings. What is a Proxy? diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 3aea7a5713..a2c2187ddd 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -180,6 +180,12 @@ interface MetaAccountDao { @Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)") suspend fun changeAccountsStatus(metaIds: List, status: MetaAccountLocal.Status) + + @Query("SELECT * FROM proxy_accounts WHERE proxyMetaId = :metaId") + fun getProxyAccountsByMetaId(metaId: Long): List + + @Query("SELECT * FROM proxy_accounts WHERE proxiedAccountId = :accountId") + fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List } class MetaAccountWithBalanceLocal( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt index d4d1d9e6f2..41772b3529 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -1,10 +1,15 @@ package io.novafoundation.nova.feature_account_api.data.repository import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.fearless_utils.runtime.AccountId interface ProxyRepository { suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List + + suspend fun getDelegatedProxyTypes(chainId: ChainId, accountId: AccountId): List } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt index ce04078543..dc80751eef 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_api.data.signer import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer @@ -8,5 +9,7 @@ interface SignerProvider { fun signerFor(metaAccount: MetaAccount): Signer - fun feeSigner(chain: Chain): Signer + fun feeSigner(chain: Chain): FeeSigner + + fun feeSigner(metaAccount: MetaAccount, chain: Chain): FeeSigner } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index 795e064fdb..5b5297510e 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -8,6 +8,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.Mnemonic import jp.co.soramitsu.fearless_utils.runtime.AccountId @@ -122,4 +123,8 @@ interface AccountRepository { ): String suspend fun isAccountExists(accountId: AccountId): Boolean + + suspend fun getProxyAccountsByMetaId(metaId: Long): List + + suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt index 211000edce..4eb5e0c4a6 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt @@ -9,24 +9,24 @@ class ProxyAccount( val proxyType: ProxyType, ) { - sealed interface ProxyType { + sealed class ProxyType(val name: String) { - object Any : ProxyType + object Any : ProxyType("Any") - object NonTransfer : ProxyType + object NonTransfer : ProxyType("NonTransfer") - object Governance : ProxyType + object Governance : ProxyType("Governance") - object Staking : ProxyType + object Staking : ProxyType("Staking") - object IdentityJudgement : ProxyType + object IdentityJudgement : ProxyType("IdentityJudgement") - object CancelProxy : ProxyType + object CancelProxy : ProxyType("CancelProxy") - object Auction : ProxyType + object Auction : ProxyType("Auction") - object NominationPools : ProxyType + object NominationPools : ProxyType("NominationPools") - class Other(val name: String) : ProxyType + class Other(name: String) : ProxyType(name) } } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt new file mode 100644 index 0000000000..da09b28463 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt @@ -0,0 +1,13 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.proxy + +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount + +interface ProxySigningPresenter { + + suspend fun requestResume(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean + + suspend fun notEnoughPermission(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount, proxyTypes: List) + + suspend fun signingIsNotSupported() +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index ab99dd0a94..76c3adde4b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -20,6 +20,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.extrinsic.multi.ExtrinsicSplitter import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -112,7 +113,7 @@ class RealExtrinsicService( chain: Chain, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, ): FeeResponse { - val extrinsic = extrinsicBuilderFactory.createForFee(chain) + val extrinsic = extrinsicBuilderFactory.createForFee(getFeeSigner(chain), chain) .also { it.formExtrinsic() } .build() @@ -123,7 +124,7 @@ class RealExtrinsicService( chain: Chain, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, ): BigInteger { - val extrinsicBuilder = extrinsicBuilderFactory.createForFee(chain) + val extrinsicBuilder = extrinsicBuilderFactory.createForFee(getFeeSigner(chain), chain) extrinsicBuilder.formExtrinsic() val extrinsic = extrinsicBuilder.build() @@ -147,7 +148,7 @@ class RealExtrinsicService( } override suspend fun estimateMultiFee(chain: Chain, formExtrinsic: FormMultiExtrinsic): BigInteger { - val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(chain) + val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(getFeeSigner(chain), chain) val extrinsics = constructSplitExtrinsics(chain, formExtrinsic, feeExtrinsicBuilderSequence) @@ -179,7 +180,7 @@ class RealExtrinsicService( val runtime = chainRegistry.getRuntime(chain.id) val callBuilder = SimpleCallBuilder(runtime).apply { formExtrinsic() } - val splitCalls = extrinsicSplitter.split(callBuilder, chain) + val splitCalls = extrinsicSplitter.split(getFeeSigner(chain), callBuilder, chain) val extrinsicBuilderIterator = extrinsicBuilderSequence.iterator() @@ -208,4 +209,9 @@ class RealExtrinsicService( return extrinsicBuilder.build(useBatchAll = true) } + + private suspend fun getFeeSigner(chain: Chain): FeeSigner { + val metaAccount = accountRepository.getSelectedMetaAccount() + return signerProvider.feeSigner(metaAccount, chain) + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index 1fcf22020f..afe58a9f7c 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.core_db.model.NodeLocal import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.account.JoinedMetaAccountInfo import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount @@ -39,21 +40,21 @@ fun mapCryptoTypeToCryptoTypeModel( ): CryptoTypeModel { val name = when (encryptionType) { CryptoType.SR25519 -> "${resourceManager.getString(R.string.sr25519_selection_title)} ${ - resourceManager.getString( - R.string.sr25519_selection_subtitle - ) + resourceManager.getString( + R.string.sr25519_selection_subtitle + ) }" CryptoType.ED25519 -> "${resourceManager.getString(R.string.ed25519_selection_title)} ${ - resourceManager.getString( - R.string.ed25519_selection_subtitle - ) + resourceManager.getString( + R.string.ed25519_selection_subtitle + ) }" CryptoType.ECDSA -> "${resourceManager.getString(R.string.ecdsa_selection_title)} ${ - resourceManager.getString( - R.string.ecdsa_selection_subtitle - ) + resourceManager.getString( + R.string.ecdsa_selection_subtitle + ) }" } @@ -129,12 +130,7 @@ fun mapMetaAccountLocalToMetaAccount( ).filterNotNull() val proxyAccount = joinedMetaAccountInfo.proxyAccountLocal?.let { - ProxyAccount( - metaId = it.proxyMetaId, - chainId = it.chainId, - proxiedAccountId = it.proxiedAccountId, - proxyType = mapProxyTypeToString(it.proxyType) - ) + mapProxyAccountFromLocal(it) } return with(joinedMetaAccountInfo.metaAccount) { @@ -174,6 +170,17 @@ fun mapMetaAccountLocalToLightMetaAccount( } } +fun mapProxyAccountFromLocal(proxyAccountLocal: ProxyAccountLocal): ProxyAccount { + return with(proxyAccountLocal) { + ProxyAccount( + metaId = proxyMetaId, + chainId = chainId, + proxiedAccountId = proxiedAccountId, + proxyType = mapProxyTypeToString(proxyType) + ) + } +} + fun mapAddAccountPayloadToAddAccountType( payload: AddAccountPayload, accountNameState: AccountNameChooserMixin.State, @@ -194,7 +201,7 @@ fun mapOptionalNameToNameChooserState(name: String?) = when (name) { else -> AccountNameChooserMixin.State.Input(name) } -private fun mapProxyTypeToString(proxyType: String): ProxyAccount.ProxyType { +fun mapProxyTypeToString(proxyType: String): ProxyAccount.ProxyType { return when (proxyType) { "Any" -> ProxyAccount.ProxyType.Any "NonTransfer" -> ProxyAccount.ProxyType.NonTransfer diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 1a00d29e0a..36db7de861 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -124,9 +124,9 @@ class RealProxySyncService( LightMetaAccount.Type.SECRETS, LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.LEDGER, - LightMetaAccount.Type.POLKADOT_VAULT -> true + LightMetaAccount.Type.POLKADOT_VAULT, + LightMetaAccount.Type.WATCH_ONLY -> true - LightMetaAccount.Type.WATCH_ONLY, LightMetaAccount.Type.PROXIED -> false } } @@ -156,9 +156,9 @@ class RealProxySyncService( return MetaAccountLocal( substratePublicKey = null, substrateCryptoType = null, - substrateAccountId = if (chain.isSubstrateBased) proxiedAccountId else null, + substrateAccountId = null, ethereumPublicKey = null, - ethereumAddress = if (chain.isEthereumBased) proxiedAccountId else null, + ethereumAddress = null, name = identity?.name ?: chain.addressOf(proxiedAccountId), parentMetaId = parentMetaId, isSelected = false, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index 30b81723a2..66e0a04b8a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -22,6 +22,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.addressIn import io.novafoundation.nova.feature_account_api.domain.model.multiChainEncryptionIn @@ -255,6 +256,14 @@ class AccountRepositoryImpl( return accountDataSource.accountExists(accountId) } + override suspend fun getProxyAccountsByMetaId(metaId: Long): List { + return accountDataSource.getProxyAccountsByMetaId(metaId) + } + + override suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List { + return accountDataSource.getProxyAccountsByProxiedAccountId(accountId) + } + override fun nodesFlow(): Flow> { return nodeDao.nodesFlow() .mapList { mapNodeLocalToNode(it) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index 81d5b3f5d3..05f58b380b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -9,7 +9,12 @@ import io.novafoundation.nova.common.data.network.runtime.binding.getTyped import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.feature_account_impl.data.mappers.mapProxyTypeToString +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource import jp.co.soramitsu.fearless_utils.runtime.AccountId @@ -17,7 +22,8 @@ import jp.co.soramitsu.fearless_utils.runtime.metadata.module import jp.co.soramitsu.fearless_utils.runtime.metadata.storage class RealProxyRepository( - private val remoteSource: StorageDataSource + private val remoteSource: StorageDataSource, + private val chainRegistry: ChainRegistry ) : ProxyRepository { override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List { @@ -37,6 +43,21 @@ class RealProxyRepository( } } + override suspend fun getDelegatedProxyTypes(chainId: ChainId, accountId: AccountId): List { + val proxies = remoteSource.query(chainId) { + runtime.metadata.module(Modules.PROXY) + .storage("Proxies") + .query( + keyArguments = arrayOf(AccountIdKey(accountId)), + binding = { result -> + bindProxyAccounts(result) + } + ) + } + + return proxies.map { mapProxyTypeToString(it.value) } + } + private suspend fun receiveAllProxies(chainId: ChainId): Map> { return remoteSource.query(chainId) { runtime.metadata.module(Modules.PROXY) @@ -68,14 +89,14 @@ class RealProxyRepository( private fun mapToProxiedWithProxies( chainId: ChainId, delegator: AccountIdKey, - proxies: ProxiedWithProxy.Proxy + proxy: ProxiedWithProxy.Proxy ): ProxiedWithProxy { return ProxiedWithProxy( proxied = ProxiedWithProxy.Proxied( accountId = delegator.value, chainId = chainId ), - proxy = proxies + proxy = proxy ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 269cc26a92..7562d2262f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -12,6 +12,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.scale.EncodableStruct @@ -89,4 +90,8 @@ interface AccountDataSource : SecretStoreV1 { ) suspend fun hasMetaAccounts(): Boolean + + suspend fun getProxyAccountsByMetaId(metaId: Long): List + + suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 9cce988484..6ecce9e611 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -24,9 +24,11 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_impl.data.mappers.mapMetaAccountLocalToLightMetaAccount import io.novafoundation.nova.feature_account_impl.data.mappers.mapMetaAccountLocalToMetaAccount import io.novafoundation.nova.feature_account_impl.data.mappers.mapMetaAccountWithBalanceFromLocal +import io.novafoundation.nova.feature_account_impl.data.mappers.mapProxyAccountFromLocal import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.AccountDataMigration import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -271,6 +273,16 @@ class AccountDataSourceImpl( return metaAccountDao.hasMetaAccounts() } + override suspend fun getProxyAccountsByMetaId(metaId: Long): List { + val proxyAccounts = metaAccountDao.getProxyAccountsByMetaId(metaId) + return proxyAccounts.map { mapProxyAccountFromLocal(it) } + } + + override suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List { + val proxyAccounts = metaAccountDao.getProxyAccountsByProxiedAccountId(accountId) + return proxyAccounts.map { mapProxyAccountFromLocal(it) } + } + private inline fun async(crossinline action: suspend () -> Unit) { GlobalScope.launch(Dispatchers.Default) { action() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt index 71334469e8..b733af1f84 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt @@ -6,17 +6,22 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.ParitySignerSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultSigner +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeSignerFactory +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySigner -import io.novafoundation.nova.runtime.extrinsic.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.feeSigner.DefaultFeeSigner +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer internal class RealSignerProvider( private val secretsSignerFactory: SecretsSignerFactory, + private val proxiedSignerFactory: ProxiedSignerFactory, private val watchOnlySigner: WatchOnlySigner, private val paritySignerSigner: ParitySignerSigner, private val polkadotVaultSigner: PolkadotVaultSigner, + private val proxiedFeeSignerFactory: ProxiedFeeSignerFactory, private val ledgerSigner: LedgerSigner, ) : SignerProvider { @@ -27,11 +32,23 @@ internal class RealSignerProvider( LightMetaAccount.Type.PARITY_SIGNER -> paritySignerSigner LightMetaAccount.Type.POLKADOT_VAULT -> polkadotVaultSigner LightMetaAccount.Type.LEDGER -> ledgerSigner - LightMetaAccount.Type.PROXIED -> TODO() + LightMetaAccount.Type.PROXIED -> proxiedSignerFactory.create(metaAccount, this) } } - override fun feeSigner(chain: Chain): Signer { - return FeeSigner(chain) + override fun feeSigner(chain: Chain): FeeSigner { + return DefaultFeeSigner(chain) + } + + override fun feeSigner(metaAccount: MetaAccount, chain: Chain): FeeSigner { + return when (metaAccount.type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.WATCH_ONLY, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.POLKADOT_VAULT, + LightMetaAccount.Type.LEDGER -> DefaultFeeSigner(chain) + + LightMetaAccount.Type.PROXIED -> proxiedFeeSignerFactory.create(metaAccount, chain, this) + } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt new file mode 100644 index 0000000000..ccf3b5e14f --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt @@ -0,0 +1,50 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Module + +class ModuleToProxyTypeMatcher(private val module: String) { + + fun matchToProxyTypes(proxyTypes: List): ProxyAccount.ProxyType? { + if (proxyTypes.isEmpty()) return null + + return proxyTypes.firstOrNull { getModulesSupportedByProxyType(it).isSupporting(module) } + } + + private fun getModulesSupportedByProxyType(proxyType: ProxyAccount.ProxyType): SupportedModules { + return when (proxyType) { + ProxyAccount.ProxyType.Any -> SupportedModules.AnyModule + ProxyAccount.ProxyType.NonTransfer -> SupportedModules.SpeificModules(Modules.STAKING, Modules.REFERENDA, Modules.NOMINATION_POOLS) + ProxyAccount.ProxyType.Governance -> SupportedModules.SpeificModules(Modules.REFERENDA) + ProxyAccount.ProxyType.Staking -> SupportedModules.SpeificModules(Modules.STAKING) + ProxyAccount.ProxyType.IdentityJudgement -> SupportedModules.SpeificModules(Modules.IDENTITY) + ProxyAccount.ProxyType.CancelProxy -> SupportedModules.SpeificModules(Modules.PROXY) + ProxyAccount.ProxyType.Auction -> SupportedModules.SpeificModules(Modules.AUCTIONS) + ProxyAccount.ProxyType.NominationPools -> SupportedModules.SpeificModules(Modules.NOMINATION_POOLS) + is ProxyAccount.ProxyType.Other -> SupportedModules.SpeificModules() + } + } +} + +private sealed interface SupportedModules { + + fun isSupporting(module: String): Boolean + + class SpeificModules(vararg modules: String) : SupportedModules { + + private val modulesSet = modules.toSet() + + override fun isSupporting(module: String): Boolean { + return modulesSet.contains(module) + } + } + + object AnyModule : SupportedModules { + override fun isSupporting(module: String) = true + } +} + +fun Module.toProxyTypeMatcher(): ModuleToProxyTypeMatcher { + return ModuleToProxyTypeMatcher(name) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt new file mode 100644 index 0000000000..be1d47ac1f --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt @@ -0,0 +1,84 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.utils.chainId +import io.novafoundation.nova.common.utils.toCallInstance +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw + +class ProxiedFeeSignerFactory( + private val accountRepository: AccountRepository +) { + + fun create(metaAccount: MetaAccount, chain: Chain, signerProvider: SignerProvider): ProxiedFeeSigner { + return ProxiedFeeSigner( + metaAccount, + chain, + signerProvider, + accountRepository, + ) + } +} + +class ProxiedFeeSigner( + private val proxiedMetaAccount: MetaAccount, + private val chain: Chain, + private val signerProvider: SignerProvider, + private val accountRepository: AccountRepository, +) : FeeSigner { + + private var proxyMetaAccount: MetaAccount? = null + private var delegate: FeeSigner? = null + + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { + val delegator = getDelegator() + + val callInstance = payloadExtrinsic.call.toCallInstance() + if (callInstance == null) { + return delegator.signExtrinsic(payloadExtrinsic) + } else { + val modifienPayloadExtrinsic = payloadExtrinsic.modifyPayload( + getProxyAccountId(), + ProxyAccount.ProxyType.Any, + callInstance + ) + + return delegator.signExtrinsic(modifienPayloadExtrinsic) + } + } + + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { + return getDelegator().signRaw(payload) + } + + override suspend fun accountId() = getDelegator().accountId() + + private suspend fun getProxyAccountId(): ByteArray { + return getProxyMetaAccount().requireAccountIdIn(chain) + } + + private suspend fun getDelegator(): FeeSigner { + if (delegate == null) { + delegate = signerProvider.feeSigner(getProxyMetaAccount(), chain) + } + + return delegate!! + } + + private suspend fun getProxyMetaAccount(): MetaAccount { + if (proxyMetaAccount == null) { + proxyMetaAccount = proxiedMetaAccount.proxy?.metaId?.let { accountRepository.getMetaAccount(it) } + } + + return proxyMetaAccount ?: throw IllegalStateException("Proxy meta account not found") + } +} 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 new file mode 100644 index 0000000000..538523e854 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt @@ -0,0 +1,110 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.base.errors.SigningCancelledException +import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.common.utils.chainId +import io.novafoundation.nova.common.utils.toCallInstance +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType +import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw + +class ProxiedSignerFactory( + private val secretStoreV2: SecretStoreV2, + private val chainRegistry: ChainRegistry, + private val accountRepository: AccountRepository, + private val proxySigningPresenter: ProxySigningPresenter, + private val proxyRepository: ProxyRepository +) { + + fun create(metaAccount: MetaAccount, signerProvider: SignerProvider): ProxiedSigner { + return ProxiedSigner( + metaAccount, + chainRegistry, + accountRepository, + signerProvider, + proxySigningPresenter, + proxyRepository + ) + } +} + +class ProxiedSigner( + private val proxiedMetaAccount: MetaAccount, + private val chainRegistry: ChainRegistry, + private val accountRepository: AccountRepository, + private val signerProvider: SignerProvider, + private val proxySigningPresenter: ProxySigningPresenter, + private val proxyRepository: ProxyRepository +) : Signer { + + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { + val proxyMetaAccount = getProxyMetaAccount() + + requestResume(proxyMetaAccount) + + val delegate = createDelegate(proxyMetaAccount) + val modifiedPayload = modifyPayload(proxyMetaAccount, payloadExtrinsic) + + return delegate.signExtrinsic(modifiedPayload) + } + + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { + throw signingNotSupported() + } + + private suspend fun createDelegate(proxyMetaAccount: MetaAccount): Signer { + return signerProvider.signerFor(proxyMetaAccount) + } + + private suspend fun modifyPayload(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic): SignerPayloadExtrinsic { + val availableProxyTypes = proxyRepository.getDelegatedProxyTypes(payload.chainId, proxiedMetaAccount.getAccountId(payload.chainId)) + + val callInstance = payload.call.toCallInstance() ?: throw IllegalStateException("Call instance is not found") + val module = callInstance.call.module + val proxyType = module.toProxyTypeMatcher() + .matchToProxyTypes(availableProxyTypes) + ?: throw notEnoughPermission(proxyMetaAccount, availableProxyTypes) + + return payload.modifyPayload(proxiedMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) + } + + private suspend fun requestResume(proxyMetaAccount: MetaAccount) { + val resume = proxySigningPresenter.requestResume(proxiedMetaAccount, proxyMetaAccount) + if (!resume) { + throw SigningCancelledException() + } + } + + private suspend fun MetaAccount.getAccountId(chainId: ChainId): ByteArray { + val chain = chainRegistry.getChain(chainId) + return requireAccountIdIn(chain) + } + + private suspend fun getProxyMetaAccount(): MetaAccount { + val proxyAccount = proxiedMetaAccount.proxy ?: throw IllegalStateException("Proxy account is not found") + return accountRepository.getMetaAccount(proxyAccount.metaId) + } + + private suspend fun notEnoughPermission(proxyMetaAccount: MetaAccount, availableProxyTypes: List): SigningCancelledException { + proxySigningPresenter.notEnoughPermission(proxiedMetaAccount, proxyMetaAccount, availableProxyTypes) + return SigningCancelledException() + } + + private suspend fun signingNotSupported(): SigningCancelledException { + proxySigningPresenter.signingIsNotSupported() + return SigningCancelledException() + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt new file mode 100644 index 0000000000..201b2ecf15 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt @@ -0,0 +1,29 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.DictEnum +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.instances.AddressInstanceConstructor +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic + +suspend fun SignerPayloadExtrinsic.modifyPayload( + accountId: AccountId, + proxyType: ProxyAccount.ProxyType, + callInstance: CallRepresentation.Instance +): SignerPayloadExtrinsic { + val callBuilder = SimpleCallBuilder(runtime) + callBuilder.addCall( + moduleName = Modules.PROXY, + callName = "proxy", + arguments = mapOf( + "real" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, accountId), + "force_proxy_type" to DictEnum.Entry(proxyType.name, null), + "call" to callInstance.call + ) + ) + + return copy(call = CallRepresentation.Instance(callBuilder.calls.first())) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index da5c888f1a..048c6b02fd 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -75,6 +75,7 @@ import io.novafoundation.nova.feature_account_api.domain.account.common.Encrypti import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity import io.novafoundation.nova.feature_account_impl.data.proxy.RealMetaAccountsUpdatesRegistry +import io.novafoundation.nova.feature_account_impl.di.modules.ProxySigningModule import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountDetailsInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory @@ -104,7 +105,7 @@ import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption import jp.co.soramitsu.fearless_utils.encrypt.junction.BIP32JunctionDecoder @Module( - includes = [SignersModule::class, WatchOnlyModule::class, ParitySignerModule::class, IdentityProviderModule::class, AdvancedEncryptionStoreModule::class] + includes = [SignersModule::class, WatchOnlyModule::class, ProxySigningModule::class, ParitySignerModule::class, IdentityProviderModule::class, AdvancedEncryptionStoreModule::class] ) class AccountFeatureModule { @@ -117,8 +118,9 @@ class AccountFeatureModule { @Provides @FeatureScope fun provideProxyRepository( - @Named(REMOTE_STORAGE_SOURCE) storageDataSource: StorageDataSource - ): ProxyRepository = RealProxyRepository(storageDataSource) + @Named(REMOTE_STORAGE_SOURCE) storageDataSource: StorageDataSource, + chainRegistry: ChainRegistry + ): ProxyRepository = RealProxyRepository(storageDataSource, chainRegistry) @Provides @FeatureScope diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt new file mode 100644 index 0000000000..be33222b6d --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt @@ -0,0 +1,22 @@ +package io.novafoundation.nova.feature_account_impl.di.modules + +import dagger.Module +import dagger.Provides +import io.novafoundation.nova.common.data.storage.Preferences +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.resources.ContextManager +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_account_impl.presentation.proxy.sign.RealProxySigningPresenter +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter + +@Module +class ProxySigningModule { + + @Provides + @FeatureScope + fun provideProxySigningPresenter( + contextManager: ContextManager, + resourceManager: ResourceManager, + preferences: Preferences + ): ProxySigningPresenter = RealProxySigningPresenter(contextManager, resourceManager, preferences) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt index a84de240a0..ca5106c618 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt @@ -8,15 +8,20 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService import io.novafoundation.nova.common.utils.DefaultMutableSharedState import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_impl.data.signer.RealSignerProvider import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.ParitySignerSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeSignerFactory +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySigner import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable @@ -38,6 +43,22 @@ class SignersModule { twoFactorVerificationService: TwoFactorVerificationService ) = SecretsSignerFactory(secretStoreV2, chainRegistry, twoFactorVerificationService) + @Provides + @FeatureScope + fun provideProxiedSignerFactory( + secretStoreV2: SecretStoreV2, + chainRegistry: ChainRegistry, + accountRepository: AccountRepository, + proxySigningPresenter: ProxySigningPresenter, + proxyRepository: ProxyRepository, + ) = ProxiedSignerFactory(secretStoreV2, chainRegistry, accountRepository, proxySigningPresenter, proxyRepository) + + @Provides + @FeatureScope + fun provideProxiedFeeSignerFactory( + accountRepository: AccountRepository + ) = ProxiedFeeSignerFactory(accountRepository) + @Provides @FeatureScope fun provideWatchOnlySigner( @@ -89,15 +110,19 @@ class SignersModule { @FeatureScope fun provideSignerProvider( secretsSignerFactory: SecretsSignerFactory, + proxiedSignerFactory: ProxiedSignerFactory, watchOnlySigner: WatchOnlySigner, paritySignerSigner: ParitySignerSigner, polkadotVaultSigner: PolkadotVaultSigner, + proxiedFeeSignerFactory: ProxiedFeeSignerFactory, ledgerSigner: LedgerSigner ): SignerProvider = RealSignerProvider( secretsSignerFactory = secretsSignerFactory, watchOnlySigner = watchOnlySigner, paritySignerSigner = paritySignerSigner, polkadotVaultSigner = polkadotVaultSigner, + proxiedSignerFactory = proxiedSignerFactory, + proxiedFeeSignerFactory = proxiedFeeSignerFactory, ledgerSigner = ledgerSigner ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/sign/notSupported/AcknowledgeSigningNotSupportedBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/sign/notSupported/AcknowledgeSigningNotSupportedBottomSheet.kt index 84d3d39f44..bf2fabfa98 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/sign/notSupported/AcknowledgeSigningNotSupportedBottomSheet.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/sign/notSupported/AcknowledgeSigningNotSupportedBottomSheet.kt @@ -19,8 +19,8 @@ class AcknowledgeSigningNotSupportedBottomSheet( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title.setText(R.string.account_parity_signer_not_supported_title) - subtitle.text = payload.message + titleView.setText(R.string.account_parity_signer_not_supported_title) + subtitleView.text = payload.message applySolidIconStyle(payload.iconRes) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt new file mode 100644 index 0000000000..7b266f7c63 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign + +import android.content.Context +import android.os.Bundle +import io.novafoundation.nova.common.view.bottomSheet.ActionNotAllowedBottomSheet +import io.novafoundation.nova.feature_account_impl.R + +class ProxySignNotEnoughPermissionBottomSheet( + context: Context, + private val subtitle: CharSequence, + onSuccess: () -> Unit +) : ActionNotAllowedBottomSheet(context, onSuccess) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + titleView.setText(R.string.proxy_signing_not_enough_permission_title) + subtitleView.setText(subtitle) + buttonView.setText(R.string.common_okay_back) + + applySolidIconStyle(R.drawable.ic_proxy) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignOperationNotSupportedBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignOperationNotSupportedBottomSheet.kt new file mode 100644 index 0000000000..0de4024e39 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignOperationNotSupportedBottomSheet.kt @@ -0,0 +1,21 @@ +package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign + +import android.content.Context +import android.os.Bundle +import io.novafoundation.nova.common.view.bottomSheet.ActionNotAllowedBottomSheet +import io.novafoundation.nova.feature_account_impl.R + +class ProxySignOperationNotSupportedBottomSheet( + context: Context, + onSuccess: () -> Unit +) : ActionNotAllowedBottomSheet(context, onSuccess) { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + titleView.setText(R.string.proxy_signing_is_not_supported_title) + subtitleView.setText(R.string.proxy_signing_is_not_supported_message) + buttonView.setText(R.string.common_okay_back) + + applySolidIconStyle(R.drawable.ic_proxy) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt new file mode 100644 index 0000000000..d66491eaf1 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt @@ -0,0 +1,49 @@ +package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign + +import android.content.Context +import android.os.Bundle +import android.widget.CheckBox +import android.widget.TextView +import io.novafoundation.nova.common.utils.WithContextExtensions +import io.novafoundation.nova.common.view.PrimaryButton +import io.novafoundation.nova.common.view.bottomSheet.BaseBottomSheet +import io.novafoundation.nova.feature_account_impl.R +import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningCancel +import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningContinue +import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningDontShowAgain +import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningMessage + +class ProxySignWarningBottomSheet( + context: Context, + private val subtitle: CharSequence, + private val onFinish: (Boolean) -> Unit, + private val dontShowAgain: () -> Unit +) : BaseBottomSheet(context, io.novafoundation.nova.common.R.style.BottomSheetDialog), WithContextExtensions by WithContextExtensions(context) { + + private var finishWithContinue = false + + init { + setContentView(R.layout.bottom_sheet_proxy_warning) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + proxySigningWarningMessage.setText(subtitle) + + proxySigningWarningContinue.setOnClickListener { + if (proxySigningWarningDontShowAgain.isChecked) { + dontShowAgain() + } + finishWithContinue = true + dismiss() + } + + proxySigningWarningCancel.setOnClickListener { + finishWithContinue = false + dismiss() + } + + setOnDismissListener { onFinish(finishWithContinue) } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt new file mode 100644 index 0000000000..4c7ecdb7d7 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt @@ -0,0 +1,105 @@ +package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign + +import android.text.SpannableStringBuilder +import io.novafoundation.nova.common.data.storage.Preferences +import io.novafoundation.nova.common.resources.ContextManager +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.colorSpan +import io.novafoundation.nova.common.utils.formatting.spannable.SpannableFormatter +import io.novafoundation.nova.common.utils.toSpannable +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.feature_account_impl.R +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +private const val KEY_DONT_SHOW_AGAIN = "proxy_sign_warning_dont_show_again" + +class RealProxySigningPresenter( + private val contextManager: ContextManager, + private val resourceManager: ResourceManager, + private val preferences: Preferences +) : ProxySigningPresenter { + + override suspend fun requestResume(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean = withContext(Dispatchers.Main) { + if (notNeedToShowWarning(proxiedMetaAccount)) { + return@withContext true + } + + val resumingAllowed = suspendCoroutine { continuation -> + ProxySignWarningBottomSheet( + context = contextManager.getActivity()!!, + subtitle = formatSubtitleForWarning(proxyMetaAccount), + onFinish = { + continuation.resume(it) + }, + dontShowAgain = { dontShowAgain(proxiedMetaAccount) } + ).show() + } + + return@withContext resumingAllowed + } + + override suspend fun notEnoughPermission( + proxiedMetaAccount: MetaAccount, + proxyMetaAccount: MetaAccount, + proxyTypes: List + ) = withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + ProxySignNotEnoughPermissionBottomSheet( + context = contextManager.getActivity()!!, + subtitle = formatNotEnoughPermissionWarning(proxiedMetaAccount, proxyMetaAccount, proxyTypes), + onSuccess = { continuation.resume(Unit) } + ).show() + } + } + + override suspend fun signingIsNotSupported() = withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + val bottomSheet = ProxySignOperationNotSupportedBottomSheet( + contextManager.getActivity()!!, + onSuccess = { continuation.resume(Unit) } + ) + bottomSheet.show() + } + } + + private fun notNeedToShowWarning(proxyMetaAccount: MetaAccount): Boolean { + return preferences.getBoolean(makePrefsKey(proxyMetaAccount), false) + } + + private fun dontShowAgain(proxyMetaAccount: MetaAccount) { + preferences.putBoolean(makePrefsKey(proxyMetaAccount), true) + } + + private fun makePrefsKey(proxyMetaAccount: MetaAccount): String { + return "${KEY_DONT_SHOW_AGAIN}_${proxyMetaAccount.id}" + } + + private fun formatSubtitleForWarning(proxyMetaAccount: MetaAccount): CharSequence { + val subtitle = resourceManager.getString(R.string.proxy_signing_warning_message) + val primaryColor = resourceManager.getColor(R.color.text_primary) + val proxyName = proxyMetaAccount.name.toSpannable(colorSpan(primaryColor)) + return SpannableFormatter.format(subtitle, proxyName) + } + + private fun formatNotEnoughPermissionWarning( + proxiedMetaAccount: MetaAccount, + proxyMetaAccount: MetaAccount, + proxyTypes: List + ): CharSequence { + val subtitle = resourceManager.getString(R.string.proxy_signing_not_enough_permission_message) + val primaryColor = resourceManager.getColor(R.color.text_primary) + + val proxiedName = proxiedMetaAccount.name.toSpannable(colorSpan(primaryColor)) + val proxyName = proxyMetaAccount.name.toSpannable(colorSpan(primaryColor)) + + val proxyTypesBuffer = SpannableStringBuilder() + val proxyTypesCharSequence = proxyTypes.joinTo(proxyTypesBuffer) { it.name.toSpannable(colorSpan(primaryColor)) } + + return SpannableFormatter.format(subtitle, proxiedName, proxyName, proxyTypesCharSequence) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/sign/WatchOnlySignBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/sign/WatchOnlySignBottomSheet.kt index 350f9f654c..05f39cc80e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/sign/WatchOnlySignBottomSheet.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/sign/WatchOnlySignBottomSheet.kt @@ -13,8 +13,8 @@ class WatchOnlySignBottomSheet( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title.setText(R.string.account_watch_key_missing_title) - subtitle.setText(R.string.account_watch_key_missing_description) + titleView.setText(R.string.account_watch_key_missing_title) + subtitleView.setText(R.string.account_watch_key_missing_description) applyDashedIconStyle(R.drawable.ic_key_missing) } diff --git a/feature-account-impl/src/main/res/layout/bottom_sheet_proxy_warning.xml b/feature-account-impl/src/main/res/layout/bottom_sheet_proxy_warning.xml new file mode 100644 index 0000000000..5c757a8f1b --- /dev/null +++ b/feature-account-impl/src/main/res/layout/bottom_sheet_proxy_warning.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/receive/view/LedgerNotSupportedWarningBottomSheet.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/receive/view/LedgerNotSupportedWarningBottomSheet.kt index d6d9acfa0a..fd7cec66d0 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/receive/view/LedgerNotSupportedWarningBottomSheet.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/receive/view/LedgerNotSupportedWarningBottomSheet.kt @@ -14,8 +14,8 @@ class LedgerNotSupportedWarningBottomSheet( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title.setText(R.string.assets_receive_ledger_not_supported_title) - subtitle.text = message + titleView.setText(R.string.assets_receive_ledger_not_supported_title) + subtitleView.text = message applySolidIconStyle(R.drawable.ic_ledger) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/sheets/AcknowledgePhishingBottomSheet.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/sheets/AcknowledgePhishingBottomSheet.kt index 84b5f403d2..eb630c6889 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/sheets/AcknowledgePhishingBottomSheet.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/sheets/AcknowledgePhishingBottomSheet.kt @@ -17,8 +17,8 @@ class AcknowledgePhishingBottomSheet( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title.setText(R.string.dapp_phishing_title) - subtitle.setText(R.string.dapp_phishing_subtitle) + titleView.setText(R.string.dapp_phishing_title) + subtitleView.setText(R.string.dapp_phishing_subtitle) applySolidIconStyle(R.drawable.ic_warning_filled) } 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 8468343e7a..d8d2fb150f 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 @@ -13,6 +13,7 @@ import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.presenatation.chain.ChainUi import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator import io.novafoundation.nova.feature_external_sign_api.model.failedSigningIfNotCancelled @@ -200,7 +201,7 @@ class PolkadotExternalSignInteractor( val accountId = chain.accountIdOf(address) val signer = if (forFee) { - signerProvider.feeSigner(chain) + signerProvider.feeSigner(resolveMetaAccount(), chain) } else { resolveWalletSigner() } 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 9b3ca28795..e83c75db5f 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 @@ -4,6 +4,8 @@ import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.core_db.dao.ChainDao import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.ext.requireGenesisHash +import io.novafoundation.nova.runtime.extrinsic.feeSigner.DefaultFeeSigner +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.mapper.toRuntimeVersion import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -26,9 +28,10 @@ class ExtrinsicBuilderFactory( * Create with special signer for fee calculation */ suspend fun createForFee( + signer: FeeSigner, chain: Chain, ): ExtrinsicBuilder { - return createMultiForFee(chain).first() + return createMultiForFee(signer, chain).first() } /** @@ -43,10 +46,9 @@ class ExtrinsicBuilderFactory( } suspend fun createMultiForFee( + signer: FeeSigner, chain: Chain, ): Sequence { - val signer = FeeSigner(chain) - return createMulti(chain, signer, signer.accountId()) } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt similarity index 89% rename from runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt rename to runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt index 20904ea7f1..47c9060e62 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.runtime.extrinsic +package io.novafoundation.nova.runtime.extrinsic.feeSigner import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -10,13 +10,12 @@ import jp.co.soramitsu.fearless_utils.encrypt.keypair.substrate.SubstrateKeypair import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.KeyPairSigner import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw private val FAKE_CRYPTO_TYPE = EncryptionType.ECDSA -class FeeSigner(private val chain: Chain) : Signer { +class DefaultFeeSigner(private val chain: Chain) : FeeSigner { private val keypair = generateFakeKeyPair() @@ -32,7 +31,7 @@ class FeeSigner(private val chain: Chain) : Signer { return signer.signRaw(payload) } - fun accountId() = chain.accountIdOf(keypair.publicKey) + override suspend fun accountId() = chain.accountIdOf(keypair.publicKey) private fun multiChainEncryption() = if (chain.isEthereumBased) { MultiChainEncryption.Ethereum diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt new file mode 100644 index 0000000000..381fcec30b --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.runtime.extrinsic.feeSigner + +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer + +interface FeeSigner : Signer { + + suspend fun accountId(): AccountId +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt index 06f1bbab70..5a52b42857 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt @@ -5,7 +5,8 @@ import io.novafoundation.nova.common.data.network.runtime.binding.Weight import io.novafoundation.nova.common.utils.times import io.novafoundation.nova.runtime.ext.requireGenesisHash import io.novafoundation.nova.runtime.extrinsic.CustomSignedExtensions -import io.novafoundation.nova.runtime.extrinsic.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.feeSigner.DefaultFeeSigner +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.network.rpc.RpcCalls import io.novafoundation.nova.runtime.repository.BlockLimitsRepository @@ -24,10 +25,11 @@ typealias SplitCalls = List> interface ExtrinsicSplitter { - suspend fun split(callBuilder: CallBuilder, chain: Chain): SplitCalls + suspend fun split(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls } private typealias CallWeightsByType = Map> + private const val LEAVE_SOME_SPACE_MULTIPLIER = 0.8 internal class RealExtrinsicSplitter( @@ -35,8 +37,8 @@ internal class RealExtrinsicSplitter( private val blockLimitsRepository: BlockLimitsRepository, ) : ExtrinsicSplitter { - override suspend fun split(callBuilder: CallBuilder, chain: Chain): SplitCalls = coroutineScope { - val weightByCallId = estimateWeightByCallType(callBuilder, chain) + override suspend fun split(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls = coroutineScope { + val weightByCallId = estimateWeightByCallType(signer, callBuilder, chain) val blockLimit = blockLimitsRepository.maxWeightForNormalExtrinsics(chain.id) * LEAVE_SOME_SPACE_MULTIPLIER @@ -51,11 +53,11 @@ internal class RealExtrinsicSplitter( } @Suppress("SuspendFunctionOnCoroutineScope") - private suspend fun CoroutineScope.estimateWeightByCallType(callBuilder: CallBuilder, chain: Chain): CallWeightsByType { + private suspend fun CoroutineScope.estimateWeightByCallType(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): CallWeightsByType { return callBuilder.calls.groupBy { it.uniqueId } .mapValues { (_, calls) -> val sample = calls.first() - val sampleExtrinsic = wrapInFakeExtrinsic(sample, callBuilder.runtime, chain) + val sampleExtrinsic = wrapInFakeExtrinsic(signer, sample, callBuilder.runtime, chain) async { rpcCalls.getExtrinsicFee(chain.id, sampleExtrinsic).weight } } @@ -90,8 +92,7 @@ internal class RealExtrinsicSplitter( return split } - private suspend fun wrapInFakeExtrinsic(call: GenericCall.Instance, runtime: RuntimeSnapshot, chain: Chain): String { - val signer = FeeSigner(chain) + private suspend fun wrapInFakeExtrinsic(signer: FeeSigner, call: GenericCall.Instance, runtime: RuntimeSnapshot, chain: Chain): String { val genesisHash = chain.requireGenesisHash().fromHex() return ExtrinsicBuilder( From 90413c04ee05fc2040e55cf5562e3890dbb82fa3 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 01:20:13 +0100 Subject: [PATCH 026/100] Run ktlint --- .../nova/common/utils/FearlessLibExt.kt | 1 - .../data/repository/ProxyRepository.kt | 1 - .../data/mappers/Mappers.kt | 18 +++++++++--------- .../data/proxy/RealProxySyncService.kt | 1 - .../data/repository/AccountRepositoryImpl.kt | 2 +- .../data/repository/RealProxyRepository.kt | 2 -- .../data/signer/proxy/ProxiedFeeSigner.kt | 2 -- .../data/signer/proxy/ProxiedSigner.kt | 2 -- .../proxy/sign/ProxySignWarningBottomSheet.kt | 3 --- .../polkadot/PolkadotExternalSignInteractor.kt | 1 - .../extrinsic/ExtrinsicBuilderFactory.kt | 1 - .../extrinsic/multi/ExtrinsicSplitter.kt | 1 - 12 files changed, 10 insertions(+), 25 deletions(-) diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index 2b5d4fc83a..bcc9cf7d0b 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -306,7 +306,6 @@ fun CallRepresentation.toCallInstance(): CallRepresentation.Instance? { return (this as? CallRepresentation.Instance) } - object Modules { const val VESTING: String = "Vesting" const val STAKING = "Staking" diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt index 41772b3529..3e18d77b7c 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -1,7 +1,6 @@ package io.novafoundation.nova.feature_account_api.data.repository import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index afe58a9f7c..113c5b8e90 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -40,21 +40,21 @@ fun mapCryptoTypeToCryptoTypeModel( ): CryptoTypeModel { val name = when (encryptionType) { CryptoType.SR25519 -> "${resourceManager.getString(R.string.sr25519_selection_title)} ${ - resourceManager.getString( - R.string.sr25519_selection_subtitle - ) + resourceManager.getString( + R.string.sr25519_selection_subtitle + ) }" CryptoType.ED25519 -> "${resourceManager.getString(R.string.ed25519_selection_title)} ${ - resourceManager.getString( - R.string.ed25519_selection_subtitle - ) + resourceManager.getString( + R.string.ed25519_selection_subtitle + ) }" CryptoType.ECDSA -> "${resourceManager.getString(R.string.ecdsa_selection_title)} ${ - resourceManager.getString( - R.string.ecdsa_selection_subtitle - ) + resourceManager.getString( + R.string.ecdsa_selection_subtitle + ) }" } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 36db7de861..5e80292b46 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -21,7 +21,6 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.runtime.ext.addressOf -import io.novafoundation.nova.runtime.ext.isSubstrateBased import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index 66e0a04b8a..8dbbc60e24 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -263,7 +263,7 @@ class AccountRepositoryImpl( override suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List { return accountDataSource.getProxyAccountsByProxiedAccountId(accountId) } - + override fun nodesFlow(): Flow> { return nodeDao.nodesFlow() .mapList { mapNodeLocalToNode(it) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index 05f58b380b..808fb07ded 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -9,10 +9,8 @@ import io.novafoundation.nova.common.data.network.runtime.binding.getTyped import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount -import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_impl.data.mappers.mapProxyTypeToString import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt index be1d47ac1f..2b4559c305 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt @@ -1,8 +1,6 @@ package io.novafoundation.nova.feature_account_impl.data.signer.proxy -import io.novafoundation.nova.common.utils.chainId import io.novafoundation.nova.common.utils.toCallInstance -import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount 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 538523e854..27da98c65f 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 @@ -9,12 +9,10 @@ import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType -import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt index d66491eaf1..b33efc7d8c 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt @@ -2,10 +2,7 @@ package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign import android.content.Context import android.os.Bundle -import android.widget.CheckBox -import android.widget.TextView import io.novafoundation.nova.common.utils.WithContextExtensions -import io.novafoundation.nova.common.view.PrimaryButton import io.novafoundation.nova.common.view.bottomSheet.BaseBottomSheet import io.novafoundation.nova.feature_account_impl.R import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningCancel 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 d8d2fb150f..a7dc60ee98 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 @@ -13,7 +13,6 @@ import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.presenatation.chain.ChainUi import io.novafoundation.nova.feature_external_sign_api.model.ExternalSignCommunicator import io.novafoundation.nova.feature_external_sign_api.model.failedSigningIfNotCancelled 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 e83c75db5f..838569bddf 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 @@ -4,7 +4,6 @@ import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.core_db.dao.ChainDao import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.ext.requireGenesisHash -import io.novafoundation.nova.runtime.extrinsic.feeSigner.DefaultFeeSigner import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.mapper.toRuntimeVersion import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt index 5a52b42857..a0a75879d2 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt @@ -5,7 +5,6 @@ import io.novafoundation.nova.common.data.network.runtime.binding.Weight import io.novafoundation.nova.common.utils.times import io.novafoundation.nova.runtime.ext.requireGenesisHash import io.novafoundation.nova.runtime.extrinsic.CustomSignedExtensions -import io.novafoundation.nova.runtime.extrinsic.feeSigner.DefaultFeeSigner import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.network.rpc.RpcCalls From 004c3e8bc9d20e8bba365242824f46c90eb3e3c0 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 01:34:08 +0100 Subject: [PATCH 027/100] Clean code --- .../nova/core_db/dao/MetaAccountDao.kt | 6 ------ .../data/repository/ProxyRepository.kt | 2 +- .../domain/interfaces/AccountRepository.kt | 5 ----- .../data/repository/AccountRepositoryImpl.kt | 9 --------- .../data/repository/RealProxyRepository.kt | 7 ++++--- .../data/repository/datasource/AccountDataSource.kt | 5 ----- .../repository/datasource/AccountDataSourceImpl.kt | 12 ------------ .../data/signer/proxy/ProxiedFeeSigner.kt | 2 +- .../data/signer/proxy/ProxiedSigner.kt | 8 ++++++-- .../data/signer/proxy/SignerPayloadModifierExt.kt | 2 +- 10 files changed, 13 insertions(+), 45 deletions(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index a2c2187ddd..3aea7a5713 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -180,12 +180,6 @@ interface MetaAccountDao { @Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)") suspend fun changeAccountsStatus(metaIds: List, status: MetaAccountLocal.Status) - - @Query("SELECT * FROM proxy_accounts WHERE proxyMetaId = :metaId") - fun getProxyAccountsByMetaId(metaId: Long): List - - @Query("SELECT * FROM proxy_accounts WHERE proxiedAccountId = :accountId") - fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List } class MetaAccountWithBalanceLocal( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt index 3e18d77b7c..0e21ebec04 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -10,5 +10,5 @@ interface ProxyRepository { suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List - suspend fun getDelegatedProxyTypes(chainId: ChainId, accountId: AccountId): List + suspend fun getDelegatedProxyTypes(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index 5b5297510e..795e064fdb 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -8,7 +8,6 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering -import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.Mnemonic import jp.co.soramitsu.fearless_utils.runtime.AccountId @@ -123,8 +122,4 @@ interface AccountRepository { ): String suspend fun isAccountExists(accountId: AccountId): Boolean - - suspend fun getProxyAccountsByMetaId(metaId: Long): List - - suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index 8dbbc60e24..30b81723a2 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -22,7 +22,6 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering -import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.addressIn import io.novafoundation.nova.feature_account_api.domain.model.multiChainEncryptionIn @@ -256,14 +255,6 @@ class AccountRepositoryImpl( return accountDataSource.accountExists(accountId) } - override suspend fun getProxyAccountsByMetaId(metaId: Long): List { - return accountDataSource.getProxyAccountsByMetaId(metaId) - } - - override suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List { - return accountDataSource.getProxyAccountsByProxiedAccountId(accountId) - } - override fun nodesFlow(): Flow> { return nodeDao.nodesFlow() .mapList { mapNodeLocalToNode(it) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index 808fb07ded..b2db7a52d8 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -41,19 +41,20 @@ class RealProxyRepository( } } - override suspend fun getDelegatedProxyTypes(chainId: ChainId, accountId: AccountId): List { + override suspend fun getDelegatedProxyTypes(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List { val proxies = remoteSource.query(chainId) { runtime.metadata.module(Modules.PROXY) .storage("Proxies") .query( - keyArguments = arrayOf(AccountIdKey(accountId)), + keyArguments = arrayOf(AccountIdKey(proxiedAccountId)), binding = { result -> bindProxyAccounts(result) } ) } - return proxies.map { mapProxyTypeToString(it.value) } + return proxies.filter { it.key == proxyAccountId.intoKey() } + .map { mapProxyTypeToString(it.value) } } private suspend fun receiveAllProxies(chainId: ChainId): Map> { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 7562d2262f..269cc26a92 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -12,7 +12,6 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering -import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.scale.EncodableStruct @@ -90,8 +89,4 @@ interface AccountDataSource : SecretStoreV1 { ) suspend fun hasMetaAccounts(): Boolean - - suspend fun getProxyAccountsByMetaId(metaId: Long): List - - suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 6ecce9e611..9cce988484 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -24,11 +24,9 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering -import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_impl.data.mappers.mapMetaAccountLocalToLightMetaAccount import io.novafoundation.nova.feature_account_impl.data.mappers.mapMetaAccountLocalToMetaAccount import io.novafoundation.nova.feature_account_impl.data.mappers.mapMetaAccountWithBalanceFromLocal -import io.novafoundation.nova.feature_account_impl.data.mappers.mapProxyAccountFromLocal import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.AccountDataMigration import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -273,16 +271,6 @@ class AccountDataSourceImpl( return metaAccountDao.hasMetaAccounts() } - override suspend fun getProxyAccountsByMetaId(metaId: Long): List { - val proxyAccounts = metaAccountDao.getProxyAccountsByMetaId(metaId) - return proxyAccounts.map { mapProxyAccountFromLocal(it) } - } - - override suspend fun getProxyAccountsByProxiedAccountId(accountId: AccountId): List { - val proxyAccounts = metaAccountDao.getProxyAccountsByProxiedAccountId(accountId) - return proxyAccounts.map { mapProxyAccountFromLocal(it) } - } - private inline fun async(crossinline action: suspend () -> Unit) { GlobalScope.launch(Dispatchers.Default) { action() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt index 2b4559c305..284f737fbb 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt @@ -44,7 +44,7 @@ class ProxiedFeeSigner( if (callInstance == null) { return delegator.signExtrinsic(payloadExtrinsic) } else { - val modifienPayloadExtrinsic = payloadExtrinsic.modifyPayload( + val modifienPayloadExtrinsic = payloadExtrinsic.wrapIntoProxyPayload( getProxyAccountId(), ProxyAccount.ProxyType.Any, callInstance 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 27da98c65f..e6cb4697e8 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 @@ -68,7 +68,11 @@ class ProxiedSigner( } private suspend fun modifyPayload(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic): SignerPayloadExtrinsic { - val availableProxyTypes = proxyRepository.getDelegatedProxyTypes(payload.chainId, proxiedMetaAccount.getAccountId(payload.chainId)) + val availableProxyTypes = proxyRepository.getDelegatedProxyTypes( + payload.chainId, + proxiedMetaAccount.getAccountId(payload.chainId), + proxyMetaAccount.getAccountId(payload.chainId) + ) val callInstance = payload.call.toCallInstance() ?: throw IllegalStateException("Call instance is not found") val module = callInstance.call.module @@ -76,7 +80,7 @@ class ProxiedSigner( .matchToProxyTypes(availableProxyTypes) ?: throw notEnoughPermission(proxyMetaAccount, availableProxyTypes) - return payload.modifyPayload(proxiedMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) + return payload.wrapIntoProxyPayload(proxiedMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) } private suspend fun requestResume(proxyMetaAccount: MetaAccount) { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt index 201b2ecf15..006a8056fb 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt @@ -9,7 +9,7 @@ import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrins import jp.co.soramitsu.fearless_utils.runtime.definitions.types.instances.AddressInstanceConstructor import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic -suspend fun SignerPayloadExtrinsic.modifyPayload( +suspend fun SignerPayloadExtrinsic.wrapIntoProxyPayload( accountId: AccountId, proxyType: ProxyAccount.ProxyType, callInstance: CallRepresentation.Instance From 00745f7e38fe9abe471b2d54b7136741b56fb65a Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 01:38:14 +0100 Subject: [PATCH 028/100] Update AccountFeatureModule.kt --- .../nova/feature_account_impl/di/AccountFeatureModule.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index 048c6b02fd..8726daef5e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -105,7 +105,14 @@ import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption import jp.co.soramitsu.fearless_utils.encrypt.junction.BIP32JunctionDecoder @Module( - includes = [SignersModule::class, WatchOnlyModule::class, ProxySigningModule::class, ParitySignerModule::class, IdentityProviderModule::class, AdvancedEncryptionStoreModule::class] + includes = [ + SignersModule::class, + WatchOnlyModule::class, + ProxySigningModule::class, + ParitySignerModule::class, + IdentityProviderModule::class, + AdvancedEncryptionStoreModule::class + ] ) class AccountFeatureModule { From b1f8bb0fd89f4882e26e4162a766d7b3120de239 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 03:07:25 +0100 Subject: [PATCH 029/100] Fixed bugs --- .../data/repository/RealProxyRepository.kt | 2 +- .../data/signer/proxy/ProxiedSigner.kt | 2 +- .../data/signer/proxy/SignerPayloadModifierExt.kt | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index b2db7a52d8..b8e9f3f3ca 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -46,7 +46,7 @@ class RealProxyRepository( runtime.metadata.module(Modules.PROXY) .storage("Proxies") .query( - keyArguments = arrayOf(AccountIdKey(proxiedAccountId)), + keyArguments = arrayOf(proxiedAccountId), binding = { result -> bindProxyAccounts(result) } 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 e6cb4697e8..5a0ccdb1c7 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 @@ -80,7 +80,7 @@ class ProxiedSigner( .matchToProxyTypes(availableProxyTypes) ?: throw notEnoughPermission(proxyMetaAccount, availableProxyTypes) - return payload.wrapIntoProxyPayload(proxiedMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) + return payload.wrapIntoProxyPayload(proxyMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) } private suspend fun requestResume(proxyMetaAccount: MetaAccount) { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt index 006a8056fb..39ce951fc4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt @@ -10,20 +10,21 @@ import jp.co.soramitsu.fearless_utils.runtime.definitions.types.instances.Addres import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic suspend fun SignerPayloadExtrinsic.wrapIntoProxyPayload( - accountId: AccountId, + proxyAccountId: AccountId, proxyType: ProxyAccount.ProxyType, callInstance: CallRepresentation.Instance ): SignerPayloadExtrinsic { + val proxiedAccountId = accountId val callBuilder = SimpleCallBuilder(runtime) callBuilder.addCall( moduleName = Modules.PROXY, callName = "proxy", arguments = mapOf( - "real" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, accountId), + "real" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, proxiedAccountId), "force_proxy_type" to DictEnum.Entry(proxyType.name, null), "call" to callInstance.call ) ) - return copy(call = CallRepresentation.Instance(callBuilder.calls.first())) + return copy(accountId = proxyAccountId, call = CallRepresentation.Instance(callBuilder.calls.first())) } From 896dc6dde0e5eae51a6d14419997a91104398e83 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 10:24:41 +0100 Subject: [PATCH 030/100] Fixed pr notes --- .../data/proxy/RealMetaAccountsUpdatesRegistry.kt | 4 +++- .../domain/MetaAccountGroupingInteractorImpl.kt | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealMetaAccountsUpdatesRegistry.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealMetaAccountsUpdatesRegistry.kt index df0bf8f1db..c03405135b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealMetaAccountsUpdatesRegistry.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealMetaAccountsUpdatesRegistry.kt @@ -13,6 +13,7 @@ class RealMetaAccountsUpdatesRegistry( override fun addMetaIds(ids: List) { val metaIdsSet = getUpdates() + .toMutableSet() metaIdsSet.addAll(ids) val metaIdsJoinedToString = metaIdsSet.joinToString(",") if (metaIdsJoinedToString.isNotEmpty()) { @@ -25,7 +26,7 @@ class RealMetaAccountsUpdatesRegistry( .map { getUpdates() } } - override fun getUpdates(): MutableSet { + override fun getUpdates(): Set { val metaIds = preferences.getString(KEY) if (metaIds.isNullOrEmpty()) return mutableSetOf() @@ -36,6 +37,7 @@ class RealMetaAccountsUpdatesRegistry( override fun remove(ids: List) { val metaIdsSet = getUpdates() + .toMutableSet() metaIdsSet.removeAll(ids) val metaIdsJoinedToString = metaIdsSet.joinToString(",") diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt index 55b3f9628e..cc81ab232b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt @@ -22,7 +22,6 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine class MetaAccountGroupingInteractorImpl( @@ -81,16 +80,16 @@ class MetaAccountGroupingInteractorImpl( val metaById = metaAccount.associateBy(MetaAccount::id) metaAccount .filter { it.type == LightMetaAccount.Type.PROXIED && updatedMetaIds.contains(it.id) } - .map { + .mapNotNull { ProxiedAndProxyMetaAccount( it, - metaById[it.proxy?.metaId] ?: error("Proxy meta account not found"), - chainsById[it.proxy?.chainId] ?: error("Proxy chain not found") + metaById[it.proxy?.metaId] ?: return@mapNotNull null, + chainsById[it.proxy?.chainId] ?: return@mapNotNull null ) } .groupBy { it.proxied.status } .toSortedMap(metaAccountStateComparator()) - }.catch { emit(sortedMapOf()) } + } } override suspend fun hasAvailableMetaAccountsForDestination(fromId: ChainId, destinationId: ChainId): Boolean { From 0f213a9340fca6822c895a1eafc89a567a95dc23 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 10:51:33 +0100 Subject: [PATCH 031/100] Fixed pr notes --- common/src/main/res/values/strings.xml | 2 -- .../account/proxy/ProxySigningPresenter.kt | 2 +- .../signer/proxy/ModuleToProxyTypeMatcher.kt | 5 +++-- .../data/signer/proxy/ProxiedFeeSigner.kt | 2 +- .../data/signer/proxy/ProxiedSigner.kt | 21 ++++++++++--------- .../di/modules/ProxySigningModule.kt | 11 +++++++++- ...ProxySignNotEnoughPermissionBottomSheet.kt | 2 +- ...oxySignOperationNotSupportedBottomSheet.kt | 21 ------------------- .../proxy/sign/ProxySignWarningBottomSheet.kt | 6 ++---- .../proxy/sign/RealProxySigningPresenter.kt | 21 ++++++++++--------- 10 files changed, 40 insertions(+), 53 deletions(-) delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignOperationNotSupportedBottomSheet.kt diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 84e2d32f9b..584297f67e 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -9,9 +9,7 @@ Oops! Not enough permission %1$s delegated %2$s only for %3$s - Signing is not supported Proxied wallets does not support signing arbitrary messages — only transactions - Okay, back Delegated accounts update Nova Wallet automatically adds delegated authorities (Proxy) to a separate category for you. You can always manage wallets in Settings. diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt index da09b28463..4abe3cdaf1 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt @@ -5,7 +5,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount interface ProxySigningPresenter { - suspend fun requestResume(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean + suspend fun acknowledgeProxyOperation(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean suspend fun notEnoughPermission(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount, proxyTypes: List) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt index ccf3b5e14f..e551728e7b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt @@ -14,7 +14,9 @@ class ModuleToProxyTypeMatcher(private val module: String) { private fun getModulesSupportedByProxyType(proxyType: ProxyAccount.ProxyType): SupportedModules { return when (proxyType) { - ProxyAccount.ProxyType.Any -> SupportedModules.AnyModule + ProxyAccount.ProxyType.Any, + is ProxyAccount.ProxyType.Other -> SupportedModules.AnyModule + ProxyAccount.ProxyType.NonTransfer -> SupportedModules.SpeificModules(Modules.STAKING, Modules.REFERENDA, Modules.NOMINATION_POOLS) ProxyAccount.ProxyType.Governance -> SupportedModules.SpeificModules(Modules.REFERENDA) ProxyAccount.ProxyType.Staking -> SupportedModules.SpeificModules(Modules.STAKING) @@ -22,7 +24,6 @@ class ModuleToProxyTypeMatcher(private val module: String) { ProxyAccount.ProxyType.CancelProxy -> SupportedModules.SpeificModules(Modules.PROXY) ProxyAccount.ProxyType.Auction -> SupportedModules.SpeificModules(Modules.AUCTIONS) ProxyAccount.ProxyType.NominationPools -> SupportedModules.SpeificModules(Modules.NOMINATION_POOLS) - is ProxyAccount.ProxyType.Other -> SupportedModules.SpeificModules() } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt index 284f737fbb..54ffa2c799 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt @@ -74,7 +74,7 @@ class ProxiedFeeSigner( private suspend fun getProxyMetaAccount(): MetaAccount { if (proxyMetaAccount == null) { - proxyMetaAccount = proxiedMetaAccount.proxy?.metaId?.let { accountRepository.getMetaAccount(it) } + proxyMetaAccount = accountRepository.getMetaAccount(proxiedMetaAccount.proxy!!.metaId) } return proxyMetaAccount ?: throw IllegalStateException("Proxy meta account not found") 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 5a0ccdb1c7..b8efa6427b 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 @@ -11,6 +11,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic @@ -51,7 +52,7 @@ class ProxiedSigner( override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { val proxyMetaAccount = getProxyMetaAccount() - requestResume(proxyMetaAccount) + acknowledgeProxyOperation(proxyMetaAccount) val delegate = createDelegate(proxyMetaAccount) val modifiedPayload = modifyPayload(proxyMetaAccount, payloadExtrinsic) @@ -60,7 +61,7 @@ class ProxiedSigner( } override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { - throw signingNotSupported() + signingNotSupported() } private suspend fun createDelegate(proxyMetaAccount: MetaAccount): Signer { @@ -74,17 +75,17 @@ class ProxiedSigner( proxyMetaAccount.getAccountId(payload.chainId) ) - val callInstance = payload.call.toCallInstance() ?: throw IllegalStateException("Call instance is not found") + val callInstance = payload.call.toCallInstance() ?: signingNotSupported() val module = callInstance.call.module val proxyType = module.toProxyTypeMatcher() .matchToProxyTypes(availableProxyTypes) - ?: throw notEnoughPermission(proxyMetaAccount, availableProxyTypes) + ?: notEnoughPermission(proxyMetaAccount, availableProxyTypes) return payload.wrapIntoProxyPayload(proxyMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) } - private suspend fun requestResume(proxyMetaAccount: MetaAccount) { - val resume = proxySigningPresenter.requestResume(proxiedMetaAccount, proxyMetaAccount) + private suspend fun acknowledgeProxyOperation(proxyMetaAccount: MetaAccount) { + val resume = proxySigningPresenter.acknowledgeProxyOperation(proxiedMetaAccount, proxyMetaAccount) if (!resume) { throw SigningCancelledException() } @@ -100,13 +101,13 @@ class ProxiedSigner( return accountRepository.getMetaAccount(proxyAccount.metaId) } - private suspend fun notEnoughPermission(proxyMetaAccount: MetaAccount, availableProxyTypes: List): SigningCancelledException { + private suspend fun notEnoughPermission(proxyMetaAccount: MetaAccount, availableProxyTypes: List): Nothing { proxySigningPresenter.notEnoughPermission(proxiedMetaAccount, proxyMetaAccount, availableProxyTypes) - return SigningCancelledException() + throw SigningCancelledException() } - private suspend fun signingNotSupported(): SigningCancelledException { + private suspend fun signingNotSupported(): Nothing { proxySigningPresenter.signingIsNotSupported() - return SigningCancelledException() + throw SigningCancelledException() } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt index be33222b6d..6b3a656952 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt @@ -8,15 +8,24 @@ import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_account_impl.presentation.proxy.sign.RealProxySigningPresenter import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.RealSigningNotSupportedPresentable +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable @Module class ProxySigningModule { + @Provides + @FeatureScope + fun provideSigningNotSupportedPresentable(contextManager: ContextManager): SigningNotSupportedPresentable { + return RealSigningNotSupportedPresentable(contextManager) + } + @Provides @FeatureScope fun provideProxySigningPresenter( contextManager: ContextManager, resourceManager: ResourceManager, + signingNotSupportedPresentable: SigningNotSupportedPresentable, preferences: Preferences - ): ProxySigningPresenter = RealProxySigningPresenter(contextManager, resourceManager, preferences) + ): ProxySigningPresenter = RealProxySigningPresenter(contextManager, resourceManager, signingNotSupportedPresentable, preferences) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt index 7b266f7c63..22e95999bc 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt @@ -16,7 +16,7 @@ class ProxySignNotEnoughPermissionBottomSheet( titleView.setText(R.string.proxy_signing_not_enough_permission_title) subtitleView.setText(subtitle) - buttonView.setText(R.string.common_okay_back) + buttonView.setText(R.string.common_ok_back) applySolidIconStyle(R.drawable.ic_proxy) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignOperationNotSupportedBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignOperationNotSupportedBottomSheet.kt deleted file mode 100644 index 0de4024e39..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignOperationNotSupportedBottomSheet.kt +++ /dev/null @@ -1,21 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign - -import android.content.Context -import android.os.Bundle -import io.novafoundation.nova.common.view.bottomSheet.ActionNotAllowedBottomSheet -import io.novafoundation.nova.feature_account_impl.R - -class ProxySignOperationNotSupportedBottomSheet( - context: Context, - onSuccess: () -> Unit -) : ActionNotAllowedBottomSheet(context, onSuccess) { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - titleView.setText(R.string.proxy_signing_is_not_supported_title) - subtitleView.setText(R.string.proxy_signing_is_not_supported_message) - buttonView.setText(R.string.common_okay_back) - - applySolidIconStyle(R.drawable.ic_proxy) - } -} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt index b33efc7d8c..7ac47a699a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt @@ -28,17 +28,15 @@ class ProxySignWarningBottomSheet( proxySigningWarningMessage.setText(subtitle) - proxySigningWarningContinue.setOnClickListener { + proxySigningWarningContinue.setDismissingClickListener { if (proxySigningWarningDontShowAgain.isChecked) { dontShowAgain() } finishWithContinue = true - dismiss() } - proxySigningWarningCancel.setOnClickListener { + proxySigningWarningCancel.setDismissingClickListener { finishWithContinue = false - dismiss() } setOnDismissListener { onFinish(finishWithContinue) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt index 4c7ecdb7d7..f8061b4585 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.coroutines.resume @@ -21,11 +22,12 @@ private const val KEY_DONT_SHOW_AGAIN = "proxy_sign_warning_dont_show_again" class RealProxySigningPresenter( private val contextManager: ContextManager, private val resourceManager: ResourceManager, + private val signingNotSupportedPresentable: SigningNotSupportedPresentable, private val preferences: Preferences ) : ProxySigningPresenter { - override suspend fun requestResume(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean = withContext(Dispatchers.Main) { - if (notNeedToShowWarning(proxiedMetaAccount)) { + override suspend fun acknowledgeProxyOperation(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean = withContext(Dispatchers.Main) { + if (noNeedToShowWarning(proxiedMetaAccount)) { return@withContext true } @@ -57,17 +59,16 @@ class RealProxySigningPresenter( } } - override suspend fun signingIsNotSupported() = withContext(Dispatchers.Main) { - suspendCoroutine { continuation -> - val bottomSheet = ProxySignOperationNotSupportedBottomSheet( - contextManager.getActivity()!!, - onSuccess = { continuation.resume(Unit) } + override suspend fun signingIsNotSupported() { + signingNotSupportedPresentable.presentSigningNotSupported( + SigningNotSupportedPresentable.Payload( + iconRes = R.drawable.ic_proxy, + message = resourceManager.getString(R.string.proxy_signing_is_not_supported_message) ) - bottomSheet.show() - } + ) } - private fun notNeedToShowWarning(proxyMetaAccount: MetaAccount): Boolean { + private fun noNeedToShowWarning(proxyMetaAccount: MetaAccount): Boolean { return preferences.getBoolean(makePrefsKey(proxyMetaAccount), false) } From ceabf232ffd86d345914e1732dac8f6b11a5dba6 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 10:54:53 +0100 Subject: [PATCH 032/100] Update ProxiedSigner.kt --- .../nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt | 1 - 1 file changed, 1 deletion(-) 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 b8efa6427b..c7ee95a352 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 @@ -11,7 +11,6 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter -import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic From f6cf6ea848a1d092bcbab2328c7100d720c55562 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 14 Dec 2023 11:06:46 +0100 Subject: [PATCH 033/100] Fixed dependencies --- .../nova/feature_account_impl/di/AccountFeatureModule.kt | 9 +++++++++ .../di/modules/ParitySignerModule.kt | 9 --------- .../di/modules/ProxySigningModule.kt | 7 ------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index 8726daef5e..dc447616ba 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferences import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.resources.ClipboardManager +import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.LanguagesHolder import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.biometry.BiometricServiceFactory @@ -85,6 +86,8 @@ import io.novafoundation.nova.feature_account_impl.presentation.account.common.l import io.novafoundation.nova.feature_account_impl.presentation.account.wallet.WalletUiUseCaseImpl import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherMixin import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherProvider +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.RealSigningNotSupportedPresentable +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.feature_account_impl.presentation.language.RealLanguageUseCase import io.novafoundation.nova.feature_account_impl.presentation.mixin.identity.RealIdentityMixinFactory import io.novafoundation.nova.feature_account_impl.presentation.mixin.selectWallet.RealRealSelectWalletMixinFactory @@ -495,4 +498,10 @@ class AccountFeatureModule { fun provideBiometricServiceFactory(accountRepository: AccountRepository): BiometricServiceFactory { return RealBiometricServiceFactory(accountRepository) } + + @Provides + @FeatureScope + fun provideSigningNotSupportedPresentable( + contextManager: ContextManager + ): SigningNotSupportedPresentable = RealSigningNotSupportedPresentable(contextManager) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt index 7ed4764507..3a89f1991e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt @@ -4,7 +4,6 @@ import dagger.Module import dagger.Provides import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin -import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.common.utils.SharedState @@ -13,8 +12,6 @@ import io.novafoundation.nova.feature_account_impl.data.repository.ParitySignerR import io.novafoundation.nova.feature_account_impl.data.repository.RealParitySignerRepository import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter -import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.RealSigningNotSupportedPresentable -import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.common.QrCodeExpiredPresentableFactory import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @@ -41,10 +38,4 @@ class ParitySignerModule { router: AccountRouter, communicator: PolkadotVaultVariantSignCommunicator ) = QrCodeExpiredPresentableFactory(resourceManager, actionAwaitableMixinFactory, router, communicator) - - @Provides - @FeatureScope - fun provideSigningNotSupportedPresentable( - contextManager: ContextManager - ): SigningNotSupportedPresentable = RealSigningNotSupportedPresentable(contextManager) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt index 6b3a656952..81805ed872 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt @@ -8,18 +8,11 @@ import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_account_impl.presentation.proxy.sign.RealProxySigningPresenter import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter -import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.RealSigningNotSupportedPresentable import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable @Module class ProxySigningModule { - @Provides - @FeatureScope - fun provideSigningNotSupportedPresentable(contextManager: ContextManager): SigningNotSupportedPresentable { - return RealSigningNotSupportedPresentable(contextManager) - } - @Provides @FeatureScope fun provideProxySigningPresenter( From 2c187e631637423ac71b58542d00f1c86845ad85 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 15 Dec 2023 03:34:31 +0100 Subject: [PATCH 034/100] Save proxieds in transaction --- .../nova/core_db/dao/MetaAccountDao.kt | 14 ++++++-- .../data/proxy/RealProxySyncService.kt | 35 ++++++++----------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 3aea7a5713..7f86c3ada6 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -79,9 +79,14 @@ private const val META_ACCOUNT_WITH_BALANCE_QUERY = """ interface MetaAccountDao { @Transaction - suspend fun insertMetaAccountWithNewPosition(metaAccount: suspend (Int) -> MetaAccountLocal): Long { - val position = nextAccountPosition() - val metaId = insertMetaAccount(metaAccount(position)) + suspend fun insertProxiedMetaAccount( + metaAccount: MetaAccountLocal, + chainAccount: (metaId: Long) -> ChainAccountLocal, + proxyAccount: (metaId: Long) -> ProxyAccountLocal + ): Long { + val metaId = insertMetaAccount(metaAccount) + insertChainAccount(chainAccount(metaId)) + insertProxy(proxyAccount(metaId)) return metaId } @@ -95,6 +100,9 @@ interface MetaAccountDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertChainAccounts(chainAccounts: List) + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertProxy(proxyLocal: ProxyAccountLocal) + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertProxies(proxiesLocal: List) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 5e80292b46..1005c3be2d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -54,28 +54,22 @@ class RealProxySyncService( val identitiesByChain = notAddedProxies.loadProxiedIdentities() val proxiedsToMetaId = notAddedProxies.map { - val identity = identitiesByChain[it.proxied.chainId]?.get(it.proxied.accountId.intoKey()) - val proxiedMetaId = accountDao.insertMetaAccountWithNewPosition { nextPosition -> - createMetaAccount(it.proxied.chainId, it.proxy.metaId, it.proxied.accountId, identity, nextPosition) - } - it to proxiedMetaId - } - - val chains = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> - val proxied = proxiedWithProxy.proxied - createChainAccount(proxiedMetaId, proxied.chainId, proxied.accountId) - } + val proxied = it.proxied + val proxy = it.proxy + val chain = chainRegistry.getChain(proxied.chainId) + val position = accountDao.nextAccountPosition() + val identity = identitiesByChain[proxied.chainId]?.get(proxied.accountId.intoKey()) + + val proxiedMetaId = accountDao.insertProxiedMetaAccount( + metaAccount = createMetaAccount(chain, proxy.metaId, proxied.accountId, identity, position), + chainAccount = { createChainAccount(it, proxied.chainId, proxied.accountId) }, + proxyAccount = { createProxyAccount(it, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) } + ) - val newProxies = proxiedsToMetaId.map { (proxiedWithProxy, proxiedMetaId) -> - val proxied = proxiedWithProxy.proxied - val proxy = proxiedWithProxy.proxy - createProxyAccount(proxiedMetaId, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) + it to proxiedMetaId } val deactivatedMetaAccountIds = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) - - accountDao.insertChainAccounts(chains) - accountDao.insertProxies(newProxies) accountDao.changeAccountsStatus(deactivatedMetaAccountIds, MetaAccountLocal.Status.DEACTIVATED) val changedMetaIds = proxiedsToMetaId.map { it.second } + deactivatedMetaAccountIds @@ -145,13 +139,12 @@ class RealProxySyncService( } private suspend fun createMetaAccount( - chainId: ChainId, + chain: Chain, parentMetaId: Long, proxiedAccountId: AccountId, identity: Identity?, position: Int ): MetaAccountLocal { - val chain = chainRegistry.getChain(chainId) return MetaAccountLocal( substratePublicKey = null, substrateCryptoType = null, @@ -167,7 +160,7 @@ class RealProxySyncService( ) } - private suspend fun createChainAccount(metaId: Long, chainId: ChainId, accountId: AccountId): ChainAccountLocal { + private fun createChainAccount(metaId: Long, chainId: ChainId, accountId: AccountId): ChainAccountLocal { return ChainAccountLocal( metaId = metaId, chainId = chainId, From f52205eda6756ebca2b966f5bd8231976d5c85fc Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 15 Dec 2023 03:49:22 +0100 Subject: [PATCH 035/100] Filter delayed proxies --- .../data/repository/ProxyRepository.kt | 2 +- .../data/proxy/RealProxySyncService.kt | 2 +- .../data/repository/RealProxyRepository.kt | 39 ++++++++++++------- 3 files changed, 28 insertions(+), 15 deletions(-) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt index 0e21ebec04..75a614bb0a 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -8,7 +8,7 @@ import jp.co.soramitsu.fearless_utils.runtime.AccountId interface ProxyRepository { - suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List + suspend fun getAllProxiesForMetaAccounts(chainId: ChainId, metaAccountIds: List): List suspend fun getDelegatedProxyTypes(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 1005c3be2d..24e37988a0 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -45,7 +45,7 @@ class RealProxySyncService( val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> - proxyRepository.getProxyDelegatorsForAccounts(chain.id, accountIds) + proxyRepository.getAllProxiesForMetaAccounts(chain.id, accountIds) } val oldProxies = accountDao.getAllProxyAccounts() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index b8e9f3f3ca..e7d1886d74 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -15,23 +15,31 @@ import io.novafoundation.nova.feature_account_impl.data.mappers.mapProxyTypeToSt import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource +import java.math.BigInteger import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.metadata.module import jp.co.soramitsu.fearless_utils.runtime.metadata.storage +private class OnChainProxyModel( + val accountId: AccountIdKey, + val proxyType: String, + val delay: BigInteger +) + class RealProxyRepository( private val remoteSource: StorageDataSource, private val chainRegistry: ChainRegistry ) : ProxyRepository { - override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List { + override suspend fun getAllProxiesForMetaAccounts(chainId: ChainId, metaAccountIds: List): List { val delegatorToProxies = receiveAllProxies(chainId) val accountIdToMetaAccounts = metaAccountIds.groupBy { it.accountId.intoKey() } return delegatorToProxies .mapNotNull { (delegator, proxies) -> - val matchedProxies = matchProxiesToAccountsAndMap(proxies, accountIdToMetaAccounts) + val notDelayedProxies = proxies.filter { it.delay == BigInteger.ZERO } + val matchedProxies = matchProxiesToAccountsAndMap(notDelayedProxies, accountIdToMetaAccounts) if (matchedProxies.isEmpty()) return@mapNotNull null @@ -53,11 +61,11 @@ class RealProxyRepository( ) } - return proxies.filter { it.key == proxyAccountId.intoKey() } - .map { mapProxyTypeToString(it.value) } + return proxies.filter { it.accountId == proxyAccountId.intoKey() } + .map { mapProxyTypeToString(it.proxyType) } } - private suspend fun receiveAllProxies(chainId: ChainId): Map> { + private suspend fun receiveAllProxies(chainId: ChainId): Map> { return remoteSource.query(chainId) { runtime.metadata.module(Modules.PROXY) .storage("Proxies") @@ -73,7 +81,7 @@ class RealProxyRepository( } } - private fun bindProxyAccounts(dynamicInstance: Any?): Map { + private fun bindProxyAccounts(dynamicInstance: Any?): List { val root = dynamicInstance.castToList() val proxies = root[0].castToList() @@ -81,8 +89,13 @@ class RealProxyRepository( val proxy = it.castToStruct() val proxyAccountId: ByteArray = proxy.getTyped("delegate") val proxyType = proxy.get("proxyType").castToDictEnum() - proxyAccountId.intoKey() to proxyType.name - }.toMap() + val delay = proxy.getTyped("delay") + OnChainProxyModel( + proxyAccountId.intoKey(), + proxyType.name, + delay + ) + } } private fun mapToProxiedWithProxies( @@ -100,17 +113,17 @@ class RealProxyRepository( } private fun matchProxiesToAccountsAndMap( - proxies: Map, + proxies: List, accountIdToMetaAccounts: Map> ): List { - return proxies.flatMap { (proxyAccountId, proxyType) -> - val matchedAccounts = accountIdToMetaAccounts[proxyAccountId] ?: return@flatMap emptyList() + return proxies.flatMap { onChainProxy -> + val matchedAccounts = accountIdToMetaAccounts[onChainProxy.accountId] ?: return@flatMap emptyList() matchedAccounts.map { ProxiedWithProxy.Proxy( - accountId = proxyAccountId.value, + accountId = onChainProxy.accountId.value, metaId = it.metaId, - proxyType = proxyType + proxyType = onChainProxy.proxyType ) } } From 8b86513ae8e05adfd09d47a68affc10d88c9dc68 Mon Sep 17 00:00:00 2001 From: antonijzelinskij <107959809+antonijzelinskij@users.noreply.github.com> Date: Fri, 15 Dec 2023 12:50:48 +0500 Subject: [PATCH 036/100] Feature/proxied signer (#1270) * Proxied signer implementation + fee signer * Run ktlint * Clean code * Update AccountFeatureModule.kt * Fixed bugs * Fixed pr notes * Update ProxiedSigner.kt * Fixed dependencies --- .../nova/common/utils/FearlessLibExt.kt | 7 ++ .../ActionNotAllowedBottomSheet.kt | 12 +- .../bottom_sheet_action_not_allowed.xml | 3 +- common/src/main/res/values/strings.xml | 10 ++ .../data/repository/ProxyRepository.kt | 4 + .../data/signer/SignerProvider.kt | 5 +- .../domain/model/ProxyAccount.kt | 20 ++-- .../account/proxy/ProxySigningPresenter.kt | 13 ++ .../data/extrinsic/RealExtrinsicService.kt | 14 ++- .../data/mappers/Mappers.kt | 21 ++-- .../data/proxy/RealProxySyncService.kt | 9 +- .../data/repository/RealProxyRepository.kt | 26 +++- .../data/signer/RealSignerProvider.kt | 25 +++- .../signer/proxy/ModuleToProxyTypeMatcher.kt | 51 ++++++++ .../data/signer/proxy/ProxiedFeeSigner.kt | 82 +++++++++++++ .../data/signer/proxy/ProxiedSigner.kt | 112 ++++++++++++++++++ .../signer/proxy/SignerPayloadModifierExt.kt | 30 +++++ .../di/AccountFeatureModule.kt | 24 +++- .../di/modules/ParitySignerModule.kt | 9 -- .../di/modules/ProxySigningModule.kt | 24 ++++ .../di/modules/SignersModule.kt | 25 ++++ ...knowledgeSigningNotSupportedBottomSheet.kt | 4 +- ...ProxySignNotEnoughPermissionBottomSheet.kt | 23 ++++ .../proxy/sign/ProxySignWarningBottomSheet.kt | 44 +++++++ .../proxy/sign/RealProxySigningPresenter.kt | 106 +++++++++++++++++ .../sign/WatchOnlySignBottomSheet.kt | 4 +- .../res/layout/bottom_sheet_proxy_warning.xml | 77 ++++++++++++ .../LedgerNotSupportedWarningBottomSheet.kt | 4 +- .../sheets/AcknowledgePhishingBottomSheet.kt | 4 +- .../PolkadotExternalSignInteractor.kt | 2 +- .../extrinsic/ExtrinsicBuilderFactory.kt | 7 +- .../DefaultFeeSigner.kt} | 7 +- .../runtime/extrinsic/feeSigner/FeeSigner.kt | 9 ++ .../extrinsic/multi/ExtrinsicSplitter.kt | 16 +-- 34 files changed, 756 insertions(+), 77 deletions(-) create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt create mode 100644 feature-account-impl/src/main/res/layout/bottom_sheet_proxy_warning.xml rename runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/{FeeSigner.kt => feeSigner/DefaultFeeSigner.kt} (89%) create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index 49830b9f2f..bcc9cf7d0b 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -53,6 +53,7 @@ import java.io.ByteArrayOutputStream import java.math.BigInteger import java.nio.ByteBuffer import java.nio.ByteOrder +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import org.web3j.crypto.Sign @@ -301,6 +302,10 @@ fun emptyEthereumAddress() = emptyEthereumAccountId().ethereumAccountIdToAddress val SignerPayloadExtrinsic.chainId: String get() = genesisHash.toHexString() +fun CallRepresentation.toCallInstance(): CallRepresentation.Instance? { + return (this as? CallRepresentation.Instance) +} + object Modules { const val VESTING: String = "Vesting" const val STAKING = "Staking" @@ -357,4 +362,6 @@ object Modules { const val UTILITY = "Utility" const val PROXY = "Proxy" + + const val AUCTIONS = "auctions" } diff --git a/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/ActionNotAllowedBottomSheet.kt b/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/ActionNotAllowedBottomSheet.kt index 4a5175efa2..edea3ba0e1 100644 --- a/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/ActionNotAllowedBottomSheet.kt +++ b/common/src/main/java/io/novafoundation/nova/common/view/bottomSheet/ActionNotAllowedBottomSheet.kt @@ -19,16 +19,16 @@ open class ActionNotAllowedBottomSheet( private val onSuccess: () -> Unit, ) : BaseBottomSheet(context, R.style.BottomSheetDialog), WithContextExtensions by WithContextExtensions(context) { - val image: ImageView + val iconView: ImageView get() = actionNotAllowedImage - val title: TextView + val titleView: TextView get() = actionNotAllowedTitle - val subtitle: TextView + val subtitleView: TextView get() = actionNotAllowedSubtitle - val button: PrimaryButton + val buttonView: PrimaryButton get() = actionNotAllowedOk init { @@ -50,13 +50,13 @@ open class ActionNotAllowedBottomSheet( super.setContentView(layoutResId) } - protected fun applySolidIconStyle(@DrawableRes src: Int) = with(image) { + protected fun applySolidIconStyle(@DrawableRes src: Int) = with(iconView) { setPadding(12.dp) setBackgroundResource(R.drawable.bg_icon_big) setImageResource(src) } - protected fun applyDashedIconStyle(@DrawableRes src: Int) = with(image) { + protected fun applyDashedIconStyle(@DrawableRes src: Int) = with(iconView) { setPadding(12.dp) setBackgroundResource(R.drawable.bg_icon_big_dashed) setImageResource(src) diff --git a/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml b/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml index f879107f9c..3b2a7610ca 100644 --- a/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml +++ b/common/src/main/res/layout/bottom_sheet_action_not_allowed.xml @@ -12,11 +12,12 @@ style="@style/Widget.Nova.Puller" android:layout_marginTop="6dp" /> - + Do not show this again + + This is Delegating (Proxied) account + Transaction will be initiated by %s as a delegated account. Network fee will be paid by delegated account. + + Oops! Not enough permission + %1$s delegated %2$s only for %3$s + + Proxied wallets does not support signing arbitrary messages — only transactions + Delegated accounts update Nova Wallet automatically adds delegated authorities (Proxy) to a separate category for you. You can always manage wallets in Settings. What is a Proxy? diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt index d4d1d9e6f2..0e21ebec04 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt @@ -2,9 +2,13 @@ package io.novafoundation.nova.feature_account_api.data.repository import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.fearless_utils.runtime.AccountId interface ProxyRepository { suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List + + suspend fun getDelegatedProxyTypes(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt index ce04078543..dc80751eef 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_api.data.signer import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer @@ -8,5 +9,7 @@ interface SignerProvider { fun signerFor(metaAccount: MetaAccount): Signer - fun feeSigner(chain: Chain): Signer + fun feeSigner(chain: Chain): FeeSigner + + fun feeSigner(metaAccount: MetaAccount, chain: Chain): FeeSigner } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt index 211000edce..4eb5e0c4a6 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/ProxyAccount.kt @@ -9,24 +9,24 @@ class ProxyAccount( val proxyType: ProxyType, ) { - sealed interface ProxyType { + sealed class ProxyType(val name: String) { - object Any : ProxyType + object Any : ProxyType("Any") - object NonTransfer : ProxyType + object NonTransfer : ProxyType("NonTransfer") - object Governance : ProxyType + object Governance : ProxyType("Governance") - object Staking : ProxyType + object Staking : ProxyType("Staking") - object IdentityJudgement : ProxyType + object IdentityJudgement : ProxyType("IdentityJudgement") - object CancelProxy : ProxyType + object CancelProxy : ProxyType("CancelProxy") - object Auction : ProxyType + object Auction : ProxyType("Auction") - object NominationPools : ProxyType + object NominationPools : ProxyType("NominationPools") - class Other(val name: String) : ProxyType + class Other(name: String) : ProxyType(name) } } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt new file mode 100644 index 0000000000..4abe3cdaf1 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt @@ -0,0 +1,13 @@ +package io.novafoundation.nova.feature_account_api.presenatation.account.proxy + +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount + +interface ProxySigningPresenter { + + suspend fun acknowledgeProxyOperation(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean + + suspend fun notEnoughPermission(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount, proxyTypes: List) + + suspend fun signingIsNotSupported() +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index ab99dd0a94..76c3adde4b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -20,6 +20,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.extrinsic.multi.ExtrinsicSplitter import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -112,7 +113,7 @@ class RealExtrinsicService( chain: Chain, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, ): FeeResponse { - val extrinsic = extrinsicBuilderFactory.createForFee(chain) + val extrinsic = extrinsicBuilderFactory.createForFee(getFeeSigner(chain), chain) .also { it.formExtrinsic() } .build() @@ -123,7 +124,7 @@ class RealExtrinsicService( chain: Chain, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, ): BigInteger { - val extrinsicBuilder = extrinsicBuilderFactory.createForFee(chain) + val extrinsicBuilder = extrinsicBuilderFactory.createForFee(getFeeSigner(chain), chain) extrinsicBuilder.formExtrinsic() val extrinsic = extrinsicBuilder.build() @@ -147,7 +148,7 @@ class RealExtrinsicService( } override suspend fun estimateMultiFee(chain: Chain, formExtrinsic: FormMultiExtrinsic): BigInteger { - val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(chain) + val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(getFeeSigner(chain), chain) val extrinsics = constructSplitExtrinsics(chain, formExtrinsic, feeExtrinsicBuilderSequence) @@ -179,7 +180,7 @@ class RealExtrinsicService( val runtime = chainRegistry.getRuntime(chain.id) val callBuilder = SimpleCallBuilder(runtime).apply { formExtrinsic() } - val splitCalls = extrinsicSplitter.split(callBuilder, chain) + val splitCalls = extrinsicSplitter.split(getFeeSigner(chain), callBuilder, chain) val extrinsicBuilderIterator = extrinsicBuilderSequence.iterator() @@ -208,4 +209,9 @@ class RealExtrinsicService( return extrinsicBuilder.build(useBatchAll = true) } + + private suspend fun getFeeSigner(chain: Chain): FeeSigner { + val metaAccount = accountRepository.getSelectedMetaAccount() + return signerProvider.feeSigner(metaAccount, chain) + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index 1fcf22020f..113c5b8e90 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.core_db.model.NodeLocal import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.account.JoinedMetaAccountInfo import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount @@ -129,12 +130,7 @@ fun mapMetaAccountLocalToMetaAccount( ).filterNotNull() val proxyAccount = joinedMetaAccountInfo.proxyAccountLocal?.let { - ProxyAccount( - metaId = it.proxyMetaId, - chainId = it.chainId, - proxiedAccountId = it.proxiedAccountId, - proxyType = mapProxyTypeToString(it.proxyType) - ) + mapProxyAccountFromLocal(it) } return with(joinedMetaAccountInfo.metaAccount) { @@ -174,6 +170,17 @@ fun mapMetaAccountLocalToLightMetaAccount( } } +fun mapProxyAccountFromLocal(proxyAccountLocal: ProxyAccountLocal): ProxyAccount { + return with(proxyAccountLocal) { + ProxyAccount( + metaId = proxyMetaId, + chainId = chainId, + proxiedAccountId = proxiedAccountId, + proxyType = mapProxyTypeToString(proxyType) + ) + } +} + fun mapAddAccountPayloadToAddAccountType( payload: AddAccountPayload, accountNameState: AccountNameChooserMixin.State, @@ -194,7 +201,7 @@ fun mapOptionalNameToNameChooserState(name: String?) = when (name) { else -> AccountNameChooserMixin.State.Input(name) } -private fun mapProxyTypeToString(proxyType: String): ProxyAccount.ProxyType { +fun mapProxyTypeToString(proxyType: String): ProxyAccount.ProxyType { return when (proxyType) { "Any" -> ProxyAccount.ProxyType.Any "NonTransfer" -> ProxyAccount.ProxyType.NonTransfer diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 1a00d29e0a..5e80292b46 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -21,7 +21,6 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.runtime.ext.addressOf -import io.novafoundation.nova.runtime.ext.isSubstrateBased import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId @@ -124,9 +123,9 @@ class RealProxySyncService( LightMetaAccount.Type.SECRETS, LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.LEDGER, - LightMetaAccount.Type.POLKADOT_VAULT -> true + LightMetaAccount.Type.POLKADOT_VAULT, + LightMetaAccount.Type.WATCH_ONLY -> true - LightMetaAccount.Type.WATCH_ONLY, LightMetaAccount.Type.PROXIED -> false } } @@ -156,9 +155,9 @@ class RealProxySyncService( return MetaAccountLocal( substratePublicKey = null, substrateCryptoType = null, - substrateAccountId = if (chain.isSubstrateBased) proxiedAccountId else null, + substrateAccountId = null, ethereumPublicKey = null, - ethereumAddress = if (chain.isEthereumBased) proxiedAccountId else null, + ethereumAddress = null, name = identity?.name ?: chain.addressOf(proxiedAccountId), parentMetaId = parentMetaId, isSelected = false, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index 81d5b3f5d3..b8e9f3f3ca 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -10,6 +10,9 @@ import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_impl.data.mappers.mapProxyTypeToString +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource import jp.co.soramitsu.fearless_utils.runtime.AccountId @@ -17,7 +20,8 @@ import jp.co.soramitsu.fearless_utils.runtime.metadata.module import jp.co.soramitsu.fearless_utils.runtime.metadata.storage class RealProxyRepository( - private val remoteSource: StorageDataSource + private val remoteSource: StorageDataSource, + private val chainRegistry: ChainRegistry ) : ProxyRepository { override suspend fun getProxyDelegatorsForAccounts(chainId: ChainId, metaAccountIds: List): List { @@ -37,6 +41,22 @@ class RealProxyRepository( } } + override suspend fun getDelegatedProxyTypes(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List { + val proxies = remoteSource.query(chainId) { + runtime.metadata.module(Modules.PROXY) + .storage("Proxies") + .query( + keyArguments = arrayOf(proxiedAccountId), + binding = { result -> + bindProxyAccounts(result) + } + ) + } + + return proxies.filter { it.key == proxyAccountId.intoKey() } + .map { mapProxyTypeToString(it.value) } + } + private suspend fun receiveAllProxies(chainId: ChainId): Map> { return remoteSource.query(chainId) { runtime.metadata.module(Modules.PROXY) @@ -68,14 +88,14 @@ class RealProxyRepository( private fun mapToProxiedWithProxies( chainId: ChainId, delegator: AccountIdKey, - proxies: ProxiedWithProxy.Proxy + proxy: ProxiedWithProxy.Proxy ): ProxiedWithProxy { return ProxiedWithProxy( proxied = ProxiedWithProxy.Proxied( accountId = delegator.value, chainId = chainId ), - proxy = proxies + proxy = proxy ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt index 71334469e8..b733af1f84 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt @@ -6,17 +6,22 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.ParitySignerSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultSigner +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeSignerFactory +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySigner -import io.novafoundation.nova.runtime.extrinsic.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.feeSigner.DefaultFeeSigner +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer internal class RealSignerProvider( private val secretsSignerFactory: SecretsSignerFactory, + private val proxiedSignerFactory: ProxiedSignerFactory, private val watchOnlySigner: WatchOnlySigner, private val paritySignerSigner: ParitySignerSigner, private val polkadotVaultSigner: PolkadotVaultSigner, + private val proxiedFeeSignerFactory: ProxiedFeeSignerFactory, private val ledgerSigner: LedgerSigner, ) : SignerProvider { @@ -27,11 +32,23 @@ internal class RealSignerProvider( LightMetaAccount.Type.PARITY_SIGNER -> paritySignerSigner LightMetaAccount.Type.POLKADOT_VAULT -> polkadotVaultSigner LightMetaAccount.Type.LEDGER -> ledgerSigner - LightMetaAccount.Type.PROXIED -> TODO() + LightMetaAccount.Type.PROXIED -> proxiedSignerFactory.create(metaAccount, this) } } - override fun feeSigner(chain: Chain): Signer { - return FeeSigner(chain) + override fun feeSigner(chain: Chain): FeeSigner { + return DefaultFeeSigner(chain) + } + + override fun feeSigner(metaAccount: MetaAccount, chain: Chain): FeeSigner { + return when (metaAccount.type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.WATCH_ONLY, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.POLKADOT_VAULT, + LightMetaAccount.Type.LEDGER -> DefaultFeeSigner(chain) + + LightMetaAccount.Type.PROXIED -> proxiedFeeSignerFactory.create(metaAccount, chain, this) + } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt new file mode 100644 index 0000000000..e551728e7b --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt @@ -0,0 +1,51 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Module + +class ModuleToProxyTypeMatcher(private val module: String) { + + fun matchToProxyTypes(proxyTypes: List): ProxyAccount.ProxyType? { + if (proxyTypes.isEmpty()) return null + + return proxyTypes.firstOrNull { getModulesSupportedByProxyType(it).isSupporting(module) } + } + + private fun getModulesSupportedByProxyType(proxyType: ProxyAccount.ProxyType): SupportedModules { + return when (proxyType) { + ProxyAccount.ProxyType.Any, + is ProxyAccount.ProxyType.Other -> SupportedModules.AnyModule + + ProxyAccount.ProxyType.NonTransfer -> SupportedModules.SpeificModules(Modules.STAKING, Modules.REFERENDA, Modules.NOMINATION_POOLS) + ProxyAccount.ProxyType.Governance -> SupportedModules.SpeificModules(Modules.REFERENDA) + ProxyAccount.ProxyType.Staking -> SupportedModules.SpeificModules(Modules.STAKING) + ProxyAccount.ProxyType.IdentityJudgement -> SupportedModules.SpeificModules(Modules.IDENTITY) + ProxyAccount.ProxyType.CancelProxy -> SupportedModules.SpeificModules(Modules.PROXY) + ProxyAccount.ProxyType.Auction -> SupportedModules.SpeificModules(Modules.AUCTIONS) + ProxyAccount.ProxyType.NominationPools -> SupportedModules.SpeificModules(Modules.NOMINATION_POOLS) + } + } +} + +private sealed interface SupportedModules { + + fun isSupporting(module: String): Boolean + + class SpeificModules(vararg modules: String) : SupportedModules { + + private val modulesSet = modules.toSet() + + override fun isSupporting(module: String): Boolean { + return modulesSet.contains(module) + } + } + + object AnyModule : SupportedModules { + override fun isSupporting(module: String) = true + } +} + +fun Module.toProxyTypeMatcher(): ModuleToProxyTypeMatcher { + return ModuleToProxyTypeMatcher(name) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt new file mode 100644 index 0000000000..54ffa2c799 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt @@ -0,0 +1,82 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.utils.toCallInstance +import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw + +class ProxiedFeeSignerFactory( + private val accountRepository: AccountRepository +) { + + fun create(metaAccount: MetaAccount, chain: Chain, signerProvider: SignerProvider): ProxiedFeeSigner { + return ProxiedFeeSigner( + metaAccount, + chain, + signerProvider, + accountRepository, + ) + } +} + +class ProxiedFeeSigner( + private val proxiedMetaAccount: MetaAccount, + private val chain: Chain, + private val signerProvider: SignerProvider, + private val accountRepository: AccountRepository, +) : FeeSigner { + + private var proxyMetaAccount: MetaAccount? = null + private var delegate: FeeSigner? = null + + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { + val delegator = getDelegator() + + val callInstance = payloadExtrinsic.call.toCallInstance() + if (callInstance == null) { + return delegator.signExtrinsic(payloadExtrinsic) + } else { + val modifienPayloadExtrinsic = payloadExtrinsic.wrapIntoProxyPayload( + getProxyAccountId(), + ProxyAccount.ProxyType.Any, + callInstance + ) + + return delegator.signExtrinsic(modifienPayloadExtrinsic) + } + } + + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { + return getDelegator().signRaw(payload) + } + + override suspend fun accountId() = getDelegator().accountId() + + private suspend fun getProxyAccountId(): ByteArray { + return getProxyMetaAccount().requireAccountIdIn(chain) + } + + private suspend fun getDelegator(): FeeSigner { + if (delegate == null) { + delegate = signerProvider.feeSigner(getProxyMetaAccount(), chain) + } + + return delegate!! + } + + private suspend fun getProxyMetaAccount(): MetaAccount { + if (proxyMetaAccount == null) { + proxyMetaAccount = accountRepository.getMetaAccount(proxiedMetaAccount.proxy!!.metaId) + } + + return proxyMetaAccount ?: throw IllegalStateException("Proxy meta account not found") + } +} 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 new file mode 100644 index 0000000000..c7ee95a352 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedSigner.kt @@ -0,0 +1,112 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.base.errors.SigningCancelledException +import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.common.utils.chainId +import io.novafoundation.nova.common.utils.toCallInstance +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw + +class ProxiedSignerFactory( + private val secretStoreV2: SecretStoreV2, + private val chainRegistry: ChainRegistry, + private val accountRepository: AccountRepository, + private val proxySigningPresenter: ProxySigningPresenter, + private val proxyRepository: ProxyRepository +) { + + fun create(metaAccount: MetaAccount, signerProvider: SignerProvider): ProxiedSigner { + return ProxiedSigner( + metaAccount, + chainRegistry, + accountRepository, + signerProvider, + proxySigningPresenter, + proxyRepository + ) + } +} + +class ProxiedSigner( + private val proxiedMetaAccount: MetaAccount, + private val chainRegistry: ChainRegistry, + private val accountRepository: AccountRepository, + private val signerProvider: SignerProvider, + private val proxySigningPresenter: ProxySigningPresenter, + private val proxyRepository: ProxyRepository +) : Signer { + + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { + val proxyMetaAccount = getProxyMetaAccount() + + acknowledgeProxyOperation(proxyMetaAccount) + + val delegate = createDelegate(proxyMetaAccount) + val modifiedPayload = modifyPayload(proxyMetaAccount, payloadExtrinsic) + + return delegate.signExtrinsic(modifiedPayload) + } + + override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { + signingNotSupported() + } + + private suspend fun createDelegate(proxyMetaAccount: MetaAccount): Signer { + return signerProvider.signerFor(proxyMetaAccount) + } + + private suspend fun modifyPayload(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic): SignerPayloadExtrinsic { + val availableProxyTypes = proxyRepository.getDelegatedProxyTypes( + payload.chainId, + proxiedMetaAccount.getAccountId(payload.chainId), + proxyMetaAccount.getAccountId(payload.chainId) + ) + + val callInstance = payload.call.toCallInstance() ?: signingNotSupported() + val module = callInstance.call.module + val proxyType = module.toProxyTypeMatcher() + .matchToProxyTypes(availableProxyTypes) + ?: notEnoughPermission(proxyMetaAccount, availableProxyTypes) + + return payload.wrapIntoProxyPayload(proxyMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) + } + + private suspend fun acknowledgeProxyOperation(proxyMetaAccount: MetaAccount) { + val resume = proxySigningPresenter.acknowledgeProxyOperation(proxiedMetaAccount, proxyMetaAccount) + if (!resume) { + throw SigningCancelledException() + } + } + + private suspend fun MetaAccount.getAccountId(chainId: ChainId): ByteArray { + val chain = chainRegistry.getChain(chainId) + return requireAccountIdIn(chain) + } + + private suspend fun getProxyMetaAccount(): MetaAccount { + val proxyAccount = proxiedMetaAccount.proxy ?: throw IllegalStateException("Proxy account is not found") + return accountRepository.getMetaAccount(proxyAccount.metaId) + } + + private suspend fun notEnoughPermission(proxyMetaAccount: MetaAccount, availableProxyTypes: List): Nothing { + proxySigningPresenter.notEnoughPermission(proxiedMetaAccount, proxyMetaAccount, availableProxyTypes) + throw SigningCancelledException() + } + + private suspend fun signingNotSupported(): Nothing { + proxySigningPresenter.signingIsNotSupported() + throw SigningCancelledException() + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt new file mode 100644 index 0000000000..39ce951fc4 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt @@ -0,0 +1,30 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.DictEnum +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.instances.AddressInstanceConstructor +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic + +suspend fun SignerPayloadExtrinsic.wrapIntoProxyPayload( + proxyAccountId: AccountId, + proxyType: ProxyAccount.ProxyType, + callInstance: CallRepresentation.Instance +): SignerPayloadExtrinsic { + val proxiedAccountId = accountId + val callBuilder = SimpleCallBuilder(runtime) + callBuilder.addCall( + moduleName = Modules.PROXY, + callName = "proxy", + arguments = mapOf( + "real" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, proxiedAccountId), + "force_proxy_type" to DictEnum.Entry(proxyType.name, null), + "call" to callInstance.call + ) + ) + + return copy(accountId = proxyAccountId, call = CallRepresentation.Instance(callBuilder.calls.first())) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index da5c888f1a..dc447616ba 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.data.storage.encrypt.EncryptedPreferences import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.resources.ClipboardManager +import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.LanguagesHolder import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.biometry.BiometricServiceFactory @@ -75,6 +76,7 @@ import io.novafoundation.nova.feature_account_api.domain.account.common.Encrypti import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity import io.novafoundation.nova.feature_account_impl.data.proxy.RealMetaAccountsUpdatesRegistry +import io.novafoundation.nova.feature_account_impl.di.modules.ProxySigningModule import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountDetailsInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory @@ -84,6 +86,8 @@ import io.novafoundation.nova.feature_account_impl.presentation.account.common.l import io.novafoundation.nova.feature_account_impl.presentation.account.wallet.WalletUiUseCaseImpl import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherMixin import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherProvider +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.RealSigningNotSupportedPresentable +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.feature_account_impl.presentation.language.RealLanguageUseCase import io.novafoundation.nova.feature_account_impl.presentation.mixin.identity.RealIdentityMixinFactory import io.novafoundation.nova.feature_account_impl.presentation.mixin.selectWallet.RealRealSelectWalletMixinFactory @@ -104,7 +108,14 @@ import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption import jp.co.soramitsu.fearless_utils.encrypt.junction.BIP32JunctionDecoder @Module( - includes = [SignersModule::class, WatchOnlyModule::class, ParitySignerModule::class, IdentityProviderModule::class, AdvancedEncryptionStoreModule::class] + includes = [ + SignersModule::class, + WatchOnlyModule::class, + ProxySigningModule::class, + ParitySignerModule::class, + IdentityProviderModule::class, + AdvancedEncryptionStoreModule::class + ] ) class AccountFeatureModule { @@ -117,8 +128,9 @@ class AccountFeatureModule { @Provides @FeatureScope fun provideProxyRepository( - @Named(REMOTE_STORAGE_SOURCE) storageDataSource: StorageDataSource - ): ProxyRepository = RealProxyRepository(storageDataSource) + @Named(REMOTE_STORAGE_SOURCE) storageDataSource: StorageDataSource, + chainRegistry: ChainRegistry + ): ProxyRepository = RealProxyRepository(storageDataSource, chainRegistry) @Provides @FeatureScope @@ -486,4 +498,10 @@ class AccountFeatureModule { fun provideBiometricServiceFactory(accountRepository: AccountRepository): BiometricServiceFactory { return RealBiometricServiceFactory(accountRepository) } + + @Provides + @FeatureScope + fun provideSigningNotSupportedPresentable( + contextManager: ContextManager + ): SigningNotSupportedPresentable = RealSigningNotSupportedPresentable(contextManager) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt index 7ed4764507..3a89f1991e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt @@ -4,7 +4,6 @@ import dagger.Module import dagger.Provides import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin -import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.common.utils.SharedState @@ -13,8 +12,6 @@ import io.novafoundation.nova.feature_account_impl.data.repository.ParitySignerR import io.novafoundation.nova.feature_account_impl.data.repository.RealParitySignerRepository import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter -import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.RealSigningNotSupportedPresentable -import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.common.QrCodeExpiredPresentableFactory import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @@ -41,10 +38,4 @@ class ParitySignerModule { router: AccountRouter, communicator: PolkadotVaultVariantSignCommunicator ) = QrCodeExpiredPresentableFactory(resourceManager, actionAwaitableMixinFactory, router, communicator) - - @Provides - @FeatureScope - fun provideSigningNotSupportedPresentable( - contextManager: ContextManager - ): SigningNotSupportedPresentable = RealSigningNotSupportedPresentable(contextManager) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt new file mode 100644 index 0000000000..81805ed872 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ProxySigningModule.kt @@ -0,0 +1,24 @@ +package io.novafoundation.nova.feature_account_impl.di.modules + +import dagger.Module +import dagger.Provides +import io.novafoundation.nova.common.data.storage.Preferences +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.resources.ContextManager +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_account_impl.presentation.proxy.sign.RealProxySigningPresenter +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable + +@Module +class ProxySigningModule { + + @Provides + @FeatureScope + fun provideProxySigningPresenter( + contextManager: ContextManager, + resourceManager: ResourceManager, + signingNotSupportedPresentable: SigningNotSupportedPresentable, + preferences: Preferences + ): ProxySigningPresenter = RealProxySigningPresenter(contextManager, resourceManager, signingNotSupportedPresentable, preferences) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt index a84de240a0..ca5106c618 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt @@ -8,15 +8,20 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService import io.novafoundation.nova.common.utils.DefaultMutableSharedState import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_impl.data.signer.RealSignerProvider import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.ParitySignerSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultSigner import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeSignerFactory +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySigner import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable @@ -38,6 +43,22 @@ class SignersModule { twoFactorVerificationService: TwoFactorVerificationService ) = SecretsSignerFactory(secretStoreV2, chainRegistry, twoFactorVerificationService) + @Provides + @FeatureScope + fun provideProxiedSignerFactory( + secretStoreV2: SecretStoreV2, + chainRegistry: ChainRegistry, + accountRepository: AccountRepository, + proxySigningPresenter: ProxySigningPresenter, + proxyRepository: ProxyRepository, + ) = ProxiedSignerFactory(secretStoreV2, chainRegistry, accountRepository, proxySigningPresenter, proxyRepository) + + @Provides + @FeatureScope + fun provideProxiedFeeSignerFactory( + accountRepository: AccountRepository + ) = ProxiedFeeSignerFactory(accountRepository) + @Provides @FeatureScope fun provideWatchOnlySigner( @@ -89,15 +110,19 @@ class SignersModule { @FeatureScope fun provideSignerProvider( secretsSignerFactory: SecretsSignerFactory, + proxiedSignerFactory: ProxiedSignerFactory, watchOnlySigner: WatchOnlySigner, paritySignerSigner: ParitySignerSigner, polkadotVaultSigner: PolkadotVaultSigner, + proxiedFeeSignerFactory: ProxiedFeeSignerFactory, ledgerSigner: LedgerSigner ): SignerProvider = RealSignerProvider( secretsSignerFactory = secretsSignerFactory, watchOnlySigner = watchOnlySigner, paritySignerSigner = paritySignerSigner, polkadotVaultSigner = polkadotVaultSigner, + proxiedSignerFactory = proxiedSignerFactory, + proxiedFeeSignerFactory = proxiedFeeSignerFactory, ledgerSigner = ledgerSigner ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/sign/notSupported/AcknowledgeSigningNotSupportedBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/sign/notSupported/AcknowledgeSigningNotSupportedBottomSheet.kt index 84d3d39f44..bf2fabfa98 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/sign/notSupported/AcknowledgeSigningNotSupportedBottomSheet.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/common/sign/notSupported/AcknowledgeSigningNotSupportedBottomSheet.kt @@ -19,8 +19,8 @@ class AcknowledgeSigningNotSupportedBottomSheet( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title.setText(R.string.account_parity_signer_not_supported_title) - subtitle.text = payload.message + titleView.setText(R.string.account_parity_signer_not_supported_title) + subtitleView.text = payload.message applySolidIconStyle(payload.iconRes) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt new file mode 100644 index 0000000000..22e95999bc --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignNotEnoughPermissionBottomSheet.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign + +import android.content.Context +import android.os.Bundle +import io.novafoundation.nova.common.view.bottomSheet.ActionNotAllowedBottomSheet +import io.novafoundation.nova.feature_account_impl.R + +class ProxySignNotEnoughPermissionBottomSheet( + context: Context, + private val subtitle: CharSequence, + onSuccess: () -> Unit +) : ActionNotAllowedBottomSheet(context, onSuccess) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + titleView.setText(R.string.proxy_signing_not_enough_permission_title) + subtitleView.setText(subtitle) + buttonView.setText(R.string.common_ok_back) + + applySolidIconStyle(R.drawable.ic_proxy) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt new file mode 100644 index 0000000000..7ac47a699a --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/ProxySignWarningBottomSheet.kt @@ -0,0 +1,44 @@ +package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign + +import android.content.Context +import android.os.Bundle +import io.novafoundation.nova.common.utils.WithContextExtensions +import io.novafoundation.nova.common.view.bottomSheet.BaseBottomSheet +import io.novafoundation.nova.feature_account_impl.R +import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningCancel +import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningContinue +import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningDontShowAgain +import kotlinx.android.synthetic.main.bottom_sheet_proxy_warning.proxySigningWarningMessage + +class ProxySignWarningBottomSheet( + context: Context, + private val subtitle: CharSequence, + private val onFinish: (Boolean) -> Unit, + private val dontShowAgain: () -> Unit +) : BaseBottomSheet(context, io.novafoundation.nova.common.R.style.BottomSheetDialog), WithContextExtensions by WithContextExtensions(context) { + + private var finishWithContinue = false + + init { + setContentView(R.layout.bottom_sheet_proxy_warning) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + proxySigningWarningMessage.setText(subtitle) + + proxySigningWarningContinue.setDismissingClickListener { + if (proxySigningWarningDontShowAgain.isChecked) { + dontShowAgain() + } + finishWithContinue = true + } + + proxySigningWarningCancel.setDismissingClickListener { + finishWithContinue = false + } + + setOnDismissListener { onFinish(finishWithContinue) } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt new file mode 100644 index 0000000000..f8061b4585 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt @@ -0,0 +1,106 @@ +package io.novafoundation.nova.feature_account_impl.presentation.proxy.sign + +import android.text.SpannableStringBuilder +import io.novafoundation.nova.common.data.storage.Preferences +import io.novafoundation.nova.common.resources.ContextManager +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.colorSpan +import io.novafoundation.nova.common.utils.formatting.spannable.SpannableFormatter +import io.novafoundation.nova.common.utils.toSpannable +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +private const val KEY_DONT_SHOW_AGAIN = "proxy_sign_warning_dont_show_again" + +class RealProxySigningPresenter( + private val contextManager: ContextManager, + private val resourceManager: ResourceManager, + private val signingNotSupportedPresentable: SigningNotSupportedPresentable, + private val preferences: Preferences +) : ProxySigningPresenter { + + override suspend fun acknowledgeProxyOperation(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean = withContext(Dispatchers.Main) { + if (noNeedToShowWarning(proxiedMetaAccount)) { + return@withContext true + } + + val resumingAllowed = suspendCoroutine { continuation -> + ProxySignWarningBottomSheet( + context = contextManager.getActivity()!!, + subtitle = formatSubtitleForWarning(proxyMetaAccount), + onFinish = { + continuation.resume(it) + }, + dontShowAgain = { dontShowAgain(proxiedMetaAccount) } + ).show() + } + + return@withContext resumingAllowed + } + + override suspend fun notEnoughPermission( + proxiedMetaAccount: MetaAccount, + proxyMetaAccount: MetaAccount, + proxyTypes: List + ) = withContext(Dispatchers.Main) { + suspendCoroutine { continuation -> + ProxySignNotEnoughPermissionBottomSheet( + context = contextManager.getActivity()!!, + subtitle = formatNotEnoughPermissionWarning(proxiedMetaAccount, proxyMetaAccount, proxyTypes), + onSuccess = { continuation.resume(Unit) } + ).show() + } + } + + override suspend fun signingIsNotSupported() { + signingNotSupportedPresentable.presentSigningNotSupported( + SigningNotSupportedPresentable.Payload( + iconRes = R.drawable.ic_proxy, + message = resourceManager.getString(R.string.proxy_signing_is_not_supported_message) + ) + ) + } + + private fun noNeedToShowWarning(proxyMetaAccount: MetaAccount): Boolean { + return preferences.getBoolean(makePrefsKey(proxyMetaAccount), false) + } + + private fun dontShowAgain(proxyMetaAccount: MetaAccount) { + preferences.putBoolean(makePrefsKey(proxyMetaAccount), true) + } + + private fun makePrefsKey(proxyMetaAccount: MetaAccount): String { + return "${KEY_DONT_SHOW_AGAIN}_${proxyMetaAccount.id}" + } + + private fun formatSubtitleForWarning(proxyMetaAccount: MetaAccount): CharSequence { + val subtitle = resourceManager.getString(R.string.proxy_signing_warning_message) + val primaryColor = resourceManager.getColor(R.color.text_primary) + val proxyName = proxyMetaAccount.name.toSpannable(colorSpan(primaryColor)) + return SpannableFormatter.format(subtitle, proxyName) + } + + private fun formatNotEnoughPermissionWarning( + proxiedMetaAccount: MetaAccount, + proxyMetaAccount: MetaAccount, + proxyTypes: List + ): CharSequence { + val subtitle = resourceManager.getString(R.string.proxy_signing_not_enough_permission_message) + val primaryColor = resourceManager.getColor(R.color.text_primary) + + val proxiedName = proxiedMetaAccount.name.toSpannable(colorSpan(primaryColor)) + val proxyName = proxyMetaAccount.name.toSpannable(colorSpan(primaryColor)) + + val proxyTypesBuffer = SpannableStringBuilder() + val proxyTypesCharSequence = proxyTypes.joinTo(proxyTypesBuffer) { it.name.toSpannable(colorSpan(primaryColor)) } + + return SpannableFormatter.format(subtitle, proxiedName, proxyName, proxyTypesCharSequence) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/sign/WatchOnlySignBottomSheet.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/sign/WatchOnlySignBottomSheet.kt index 350f9f654c..05f39cc80e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/sign/WatchOnlySignBottomSheet.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/sign/WatchOnlySignBottomSheet.kt @@ -13,8 +13,8 @@ class WatchOnlySignBottomSheet( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title.setText(R.string.account_watch_key_missing_title) - subtitle.setText(R.string.account_watch_key_missing_description) + titleView.setText(R.string.account_watch_key_missing_title) + subtitleView.setText(R.string.account_watch_key_missing_description) applyDashedIconStyle(R.drawable.ic_key_missing) } diff --git a/feature-account-impl/src/main/res/layout/bottom_sheet_proxy_warning.xml b/feature-account-impl/src/main/res/layout/bottom_sheet_proxy_warning.xml new file mode 100644 index 0000000000..5c757a8f1b --- /dev/null +++ b/feature-account-impl/src/main/res/layout/bottom_sheet_proxy_warning.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/receive/view/LedgerNotSupportedWarningBottomSheet.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/receive/view/LedgerNotSupportedWarningBottomSheet.kt index d6d9acfa0a..fd7cec66d0 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/receive/view/LedgerNotSupportedWarningBottomSheet.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/receive/view/LedgerNotSupportedWarningBottomSheet.kt @@ -14,8 +14,8 @@ class LedgerNotSupportedWarningBottomSheet( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title.setText(R.string.assets_receive_ledger_not_supported_title) - subtitle.text = message + titleView.setText(R.string.assets_receive_ledger_not_supported_title) + subtitleView.text = message applySolidIconStyle(R.drawable.ic_ledger) } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/sheets/AcknowledgePhishingBottomSheet.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/sheets/AcknowledgePhishingBottomSheet.kt index 84b5f403d2..eb630c6889 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/sheets/AcknowledgePhishingBottomSheet.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/browser/main/sheets/AcknowledgePhishingBottomSheet.kt @@ -17,8 +17,8 @@ class AcknowledgePhishingBottomSheet( override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - title.setText(R.string.dapp_phishing_title) - subtitle.setText(R.string.dapp_phishing_subtitle) + titleView.setText(R.string.dapp_phishing_title) + subtitleView.setText(R.string.dapp_phishing_subtitle) applySolidIconStyle(R.drawable.ic_warning_filled) } 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 8468343e7a..a7dc60ee98 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 @@ -200,7 +200,7 @@ class PolkadotExternalSignInteractor( val accountId = chain.accountIdOf(address) val signer = if (forFee) { - signerProvider.feeSigner(chain) + signerProvider.feeSigner(resolveMetaAccount(), chain) } else { resolveWalletSigner() } 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 9b3ca28795..838569bddf 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 @@ -4,6 +4,7 @@ import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.core_db.dao.ChainDao import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.ext.requireGenesisHash +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.mapper.toRuntimeVersion import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -26,9 +27,10 @@ class ExtrinsicBuilderFactory( * Create with special signer for fee calculation */ suspend fun createForFee( + signer: FeeSigner, chain: Chain, ): ExtrinsicBuilder { - return createMultiForFee(chain).first() + return createMultiForFee(signer, chain).first() } /** @@ -43,10 +45,9 @@ class ExtrinsicBuilderFactory( } suspend fun createMultiForFee( + signer: FeeSigner, chain: Chain, ): Sequence { - val signer = FeeSigner(chain) - return createMulti(chain, signer, signer.accountId()) } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt similarity index 89% rename from runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt rename to runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt index 20904ea7f1..47c9060e62 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/FeeSigner.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.runtime.extrinsic +package io.novafoundation.nova.runtime.extrinsic.feeSigner import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -10,13 +10,12 @@ import jp.co.soramitsu.fearless_utils.encrypt.keypair.substrate.SubstrateKeypair import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.KeyPairSigner import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw private val FAKE_CRYPTO_TYPE = EncryptionType.ECDSA -class FeeSigner(private val chain: Chain) : Signer { +class DefaultFeeSigner(private val chain: Chain) : FeeSigner { private val keypair = generateFakeKeyPair() @@ -32,7 +31,7 @@ class FeeSigner(private val chain: Chain) : Signer { return signer.signRaw(payload) } - fun accountId() = chain.accountIdOf(keypair.publicKey) + override suspend fun accountId() = chain.accountIdOf(keypair.publicKey) private fun multiChainEncryption() = if (chain.isEthereumBased) { MultiChainEncryption.Ethereum diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt new file mode 100644 index 0000000000..381fcec30b --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.runtime.extrinsic.feeSigner + +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer + +interface FeeSigner : Signer { + + suspend fun accountId(): AccountId +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt index 06f1bbab70..a0a75879d2 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt @@ -5,7 +5,7 @@ import io.novafoundation.nova.common.data.network.runtime.binding.Weight import io.novafoundation.nova.common.utils.times import io.novafoundation.nova.runtime.ext.requireGenesisHash import io.novafoundation.nova.runtime.extrinsic.CustomSignedExtensions -import io.novafoundation.nova.runtime.extrinsic.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.network.rpc.RpcCalls import io.novafoundation.nova.runtime.repository.BlockLimitsRepository @@ -24,10 +24,11 @@ typealias SplitCalls = List> interface ExtrinsicSplitter { - suspend fun split(callBuilder: CallBuilder, chain: Chain): SplitCalls + suspend fun split(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls } private typealias CallWeightsByType = Map> + private const val LEAVE_SOME_SPACE_MULTIPLIER = 0.8 internal class RealExtrinsicSplitter( @@ -35,8 +36,8 @@ internal class RealExtrinsicSplitter( private val blockLimitsRepository: BlockLimitsRepository, ) : ExtrinsicSplitter { - override suspend fun split(callBuilder: CallBuilder, chain: Chain): SplitCalls = coroutineScope { - val weightByCallId = estimateWeightByCallType(callBuilder, chain) + override suspend fun split(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls = coroutineScope { + val weightByCallId = estimateWeightByCallType(signer, callBuilder, chain) val blockLimit = blockLimitsRepository.maxWeightForNormalExtrinsics(chain.id) * LEAVE_SOME_SPACE_MULTIPLIER @@ -51,11 +52,11 @@ internal class RealExtrinsicSplitter( } @Suppress("SuspendFunctionOnCoroutineScope") - private suspend fun CoroutineScope.estimateWeightByCallType(callBuilder: CallBuilder, chain: Chain): CallWeightsByType { + private suspend fun CoroutineScope.estimateWeightByCallType(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): CallWeightsByType { return callBuilder.calls.groupBy { it.uniqueId } .mapValues { (_, calls) -> val sample = calls.first() - val sampleExtrinsic = wrapInFakeExtrinsic(sample, callBuilder.runtime, chain) + val sampleExtrinsic = wrapInFakeExtrinsic(signer, sample, callBuilder.runtime, chain) async { rpcCalls.getExtrinsicFee(chain.id, sampleExtrinsic).weight } } @@ -90,8 +91,7 @@ internal class RealExtrinsicSplitter( return split } - private suspend fun wrapInFakeExtrinsic(call: GenericCall.Instance, runtime: RuntimeSnapshot, chain: Chain): String { - val signer = FeeSigner(chain) + private suspend fun wrapInFakeExtrinsic(signer: FeeSigner, call: GenericCall.Instance, runtime: RuntimeSnapshot, chain: Chain): String { val genesisHash = chain.requireGenesisHash().fromHex() return ExtrinsicBuilder( From 55a7130b1dbec6bd26e5438317594ff21c7b33c2 Mon Sep 17 00:00:00 2001 From: Valentun Date: Fri, 15 Dec 2023 12:12:40 +0300 Subject: [PATCH 037/100] Fix merge conflicts --- .../java/io/novafoundation/nova/core_db/AppDatabase.kt | 8 ++++---- .../nova/core_db/migrations/53_54_AddProxyAccount.kt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt index 601a1de9a2..743ef027cb 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/AppDatabase.kt @@ -65,7 +65,7 @@ import io.novafoundation.nova.core_db.migrations.AddMetaAccountType_14_15 import io.novafoundation.nova.core_db.migrations.AddNfts_5_6 import io.novafoundation.nova.core_db.migrations.AddNodeSelectionStrategyField_38_39 import io.novafoundation.nova.core_db.migrations.AddPoolIdToOperations_46_47 -import io.novafoundation.nova.core_db.migrations.AddProxyAccount_53_54 +import io.novafoundation.nova.core_db.migrations.AddProxyAccount_54_55 import io.novafoundation.nova.core_db.migrations.AddRewardAccountToStakingDashboard_43_44 import io.novafoundation.nova.core_db.migrations.AddRuntimeFlagToChains_36_37 import io.novafoundation.nova.core_db.migrations.AddSitePhishing_6_7 @@ -117,7 +117,6 @@ import io.novafoundation.nova.core_db.model.StakingRewardPeriodLocal import io.novafoundation.nova.core_db.model.StorageEntryLocal import io.novafoundation.nova.core_db.model.TokenLocal import io.novafoundation.nova.core_db.model.TotalRewardLocal -import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.WalletConnectPairingLocal import io.novafoundation.nova.core_db.model.chain.ChainAssetLocal import io.novafoundation.nova.core_db.model.chain.ChainExplorerLocal @@ -125,6 +124,7 @@ import io.novafoundation.nova.core_db.model.chain.ChainExternalApiLocal import io.novafoundation.nova.core_db.model.chain.ChainLocal import io.novafoundation.nova.core_db.model.chain.ChainNodeLocal import io.novafoundation.nova.core_db.model.chain.ChainRuntimeInfoLocal +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal import io.novafoundation.nova.core_db.model.operation.DirectRewardTypeLocal @@ -135,7 +135,7 @@ import io.novafoundation.nova.core_db.model.operation.SwapTypeLocal import io.novafoundation.nova.core_db.model.operation.TransferTypeLocal @Database( - version = 54, + version = 55, entities = [ AccountLocal::class, NodeLocal::class, @@ -225,7 +225,7 @@ abstract class AppDatabase : RoomDatabase() { .addMigrations(AddRewardAccountToStakingDashboard_43_44, AddStakingTypeToTotalRewards_44_45, AddExternalBalances_45_46) .addMigrations(AddPoolIdToOperations_46_47, AddEventIdToOperation_47_48, AddSwapOption_48_49) .addMigrations(RefactorOperations_49_50, AddTransactionVersionToRuntime_50_51) - .addMigrations(ChangeSessionTopicToParing_52_53, AddConnectionStateToChains_53_54, AddProxyAccount_53_54) + .addMigrations(ChangeSessionTopicToParing_52_53, AddConnectionStateToChains_53_54, AddProxyAccount_54_55) .build() } return instance!! diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt index cadeb509aa..78474771ac 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/migrations/53_54_AddProxyAccount.kt @@ -3,7 +3,7 @@ package io.novafoundation.nova.core_db.migrations import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase -val AddProxyAccount_53_54 = object : Migration(53, 54) { +val AddProxyAccount_54_55 = object : Migration(54, 55) { override fun migrate(database: SupportSQLiteDatabase) { database.execSQL( From 9a4423d31e8ace6ecaf6c5555c2f75de30489dcf Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Fri, 15 Dec 2023 11:01:32 +0100 Subject: [PATCH 038/100] Fixed delegated updates item subtitle --- .../common/listing/DelegatedMetaAccountUpdatesListingMixin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt index 1f7dd35fe4..52e7c85aba 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt @@ -77,7 +77,7 @@ private class DelegatedMetaAccountUpdatesListingMixin( proxiedWithProxy: ProxiedAndProxyMetaAccount ): CharSequence { val proxy = proxiedWithProxy.proxied.proxy ?: return proxiedWithProxy.proxiedAddress() // fallback - return proxyFormatter.mapProxyMetaAccountSubtitle(proxiedWithProxy.proxied, proxy) + return proxyFormatter.mapProxyMetaAccountSubtitle(proxiedWithProxy.proxy, proxy) } private fun ProxiedAndProxyMetaAccount.proxiedAddress(): String { From b15a8dc7caf29f325ad62fe5dc53aa287845e071 Mon Sep 17 00:00:00 2001 From: Valentun Date: Fri, 15 Dec 2023 15:31:49 +0300 Subject: [PATCH 039/100] Fix signing --- build.gradle | 2 +- .../data/signer/SignerProvider.kt | 9 +-- .../transaction/RealEvmTransactionService.kt | 2 +- .../data/extrinsic/RealExtrinsicService.kt | 12 ++-- .../data/signer/LeafSigner.kt | 16 +++++ .../data/signer/RealSignerProvider.kt | 35 ++++------ .../data/signer/SeparateFlowSigner.kt | 6 +- .../data/signer/ledger/LedgerSigner.kt | 22 ++++++- .../paritySigner/PolkadotVaultSigner.kt | 41 +++++++++++- .../data/signer/proxy/ProxiedFeeSigner.kt | 43 +++++++----- .../data/signer/proxy/ProxiedSigner.kt | 66 ++++++++++++------- .../signer/proxy/SignerPayloadModifierExt.kt | 24 ++++++- .../data/signer/secrets/SecretsSigner.kt | 13 ++-- .../data/signer/watchOnly/WatchOnlySigner.kt | 17 ++++- .../di/modules/SignersModule.kt | 56 ++++++---------- .../di/ExternalSignFeatureDependencies.kt | 3 + .../domain/sign/BaseExternalSignInteractor.kt | 4 +- .../PolkadotExternalSignInteractor.kt | 3 +- .../extrinsic/ExtrinsicBuilderFactory.kt | 17 +++-- .../runtime/extrinsic/feeSigner/FeeSigner.kt | 9 --- .../extrinsic/multi/ExtrinsicSplitter.kt | 15 +++-- .../{feeSigner => signer}/DefaultFeeSigner.kt | 12 +++- .../runtime/extrinsic/signer/NovaSigner.kt | 10 +++ 23 files changed, 281 insertions(+), 156 deletions(-) create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt delete mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt rename runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/{feeSigner => signer}/DefaultFeeSigner.kt (83%) create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/NovaSigner.kt diff --git a/build.gradle b/build.gradle index fe4995c43a..c6ef03a25a 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,7 @@ buildscript { web3jVersion = '4.9.5' - fearlessLibVersion = '1.11.0' + fearlessLibVersion = '1.11.1' gifVersion = '1.2.19' diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt index dc80751eef..35291e260c 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt @@ -1,15 +1,12 @@ package io.novafoundation.nova.feature_account_api.data.signer import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer interface SignerProvider { - fun signerFor(metaAccount: MetaAccount): Signer + fun signerFor(metaAccount: MetaAccount): NovaSigner - fun feeSigner(chain: Chain): FeeSigner - - fun feeSigner(metaAccount: MetaAccount, chain: Chain): FeeSigner + fun feeSigner(metaAccount: MetaAccount, chain: Chain): NovaSigner } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index 2c6a0bf722..c8d51cf593 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -19,9 +19,9 @@ import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.runtime.ethereum.sendSuspend import io.novafoundation.nova.runtime.ethereum.transaction.builder.EvmTransactionBuilder import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw import org.web3j.crypto.RawTransaction diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index 480380eb47..46e7d064b1 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -20,9 +20,9 @@ import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus -import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner import io.novafoundation.nova.runtime.extrinsic.multi.ExtrinsicSplitter import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.getRuntime @@ -197,20 +197,20 @@ class RealExtrinsicService( private suspend fun buildExtrinsic( chain: Chain, - accountId: ByteArray, + selectedAccountId: ByteArray, formExtrinsic: FormExtrinsicWithOrigin, ): String { - val metaAccount = accountRepository.findMetaAccount(accountId) ?: error("No meta account found accessing ${accountId.toHexString()}") + val metaAccount = accountRepository.findMetaAccount(selectedAccountId) ?: error("No meta account found accessing ${selectedAccountId.toHexString()}") val signer = signerProvider.signerFor(metaAccount) - val extrinsicBuilder = extrinsicBuilderFactory.create(chain, signer, accountId) + val extrinsicBuilder = extrinsicBuilderFactory.create(chain, signer, selectedAccountId) - extrinsicBuilder.formExtrinsic(accountId) + extrinsicBuilder.formExtrinsic(selectedAccountId) return extrinsicBuilder.build(useBatchAll = true) } - private suspend fun getFeeSigner(chain: Chain): FeeSigner { + private suspend fun getFeeSigner(chain: Chain): NovaSigner { val metaAccount = accountRepository.getSelectedMetaAccount() return signerProvider.feeSigner(metaAccount, chain) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt new file mode 100644 index 0000000000..5c4d483d47 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.feature_account_impl.data.signer + +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +abstract class LeafSigner( + private val metaAccount: MetaAccount, +): NovaSigner { + + override suspend fun signerAccountId(chain: Chain): AccountId { + return metaAccount.requireAccountIdIn(chain) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt index b733af1f84..3de19b9e33 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt @@ -3,44 +3,37 @@ package io.novafoundation.nova.feature_account_impl.data.signer import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner -import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.ParitySignerSigner -import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultSigner +import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSignerFactory +import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory -import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySigner -import io.novafoundation.nova.runtime.extrinsic.feeSigner.DefaultFeeSigner -import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner +import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySignerFactory +import io.novafoundation.nova.runtime.extrinsic.signer.DefaultFeeSigner +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer internal class RealSignerProvider( private val secretsSignerFactory: SecretsSignerFactory, private val proxiedSignerFactory: ProxiedSignerFactory, - private val watchOnlySigner: WatchOnlySigner, - private val paritySignerSigner: ParitySignerSigner, - private val polkadotVaultSigner: PolkadotVaultSigner, + private val watchOnlySigner: WatchOnlySignerFactory, + private val polkadotVaultSignerFactory: PolkadotVaultVariantSignerFactory, private val proxiedFeeSignerFactory: ProxiedFeeSignerFactory, - private val ledgerSigner: LedgerSigner, + private val ledgerSignerFactory: LedgerSignerFactory, ) : SignerProvider { - override fun signerFor(metaAccount: MetaAccount): Signer { + override fun signerFor(metaAccount: MetaAccount): NovaSigner { return when (metaAccount.type) { LightMetaAccount.Type.SECRETS -> secretsSignerFactory.create(metaAccount) - LightMetaAccount.Type.WATCH_ONLY -> watchOnlySigner - LightMetaAccount.Type.PARITY_SIGNER -> paritySignerSigner - LightMetaAccount.Type.POLKADOT_VAULT -> polkadotVaultSigner - LightMetaAccount.Type.LEDGER -> ledgerSigner + LightMetaAccount.Type.WATCH_ONLY -> watchOnlySigner.create(metaAccount) + LightMetaAccount.Type.PARITY_SIGNER -> polkadotVaultSignerFactory.createParitySigner(metaAccount) + LightMetaAccount.Type.POLKADOT_VAULT -> polkadotVaultSignerFactory.createPolkadotVault(metaAccount) + LightMetaAccount.Type.LEDGER -> ledgerSignerFactory.create(metaAccount) LightMetaAccount.Type.PROXIED -> proxiedSignerFactory.create(metaAccount, this) } } - override fun feeSigner(chain: Chain): FeeSigner { - return DefaultFeeSigner(chain) - } - - override fun feeSigner(metaAccount: MetaAccount, chain: Chain): FeeSigner { + override fun feeSigner(metaAccount: MetaAccount, chain: Chain): NovaSigner { return when (metaAccount.type) { LightMetaAccount.Type.SECRETS, LightMetaAccount.Type.WATCH_ONLY, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt index 83df5b4a9e..fcb1ff9a04 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt @@ -2,13 +2,12 @@ package io.novafoundation.nova.feature_account_impl.data.signer import io.novafoundation.nova.common.base.errors.SigningCancelledException import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenCommunicator import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenRequester import io.novafoundation.nova.feature_account_api.presenatation.sign.SignatureWrapper import io.novafoundation.nova.feature_account_api.presenatation.sign.awaitConfirmation -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -16,7 +15,8 @@ import kotlinx.coroutines.withContext abstract class SeparateFlowSigner( private val signingSharedState: MutableSharedState, private val signFlowRequester: SignInterScreenRequester, -) : Signer { + metaAccount: MetaAccount +) : LeafSigner(metaAccount) { override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { signingSharedState.set(payloadExtrinsic) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt index ba5c05dbd4..785c9d79df 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_account_impl.data.signer.ledger import io.novafoundation.nova.common.base.errors.SigningCancelledException import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenRequester import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.data.signer.SeparateFlowSigner @@ -11,12 +12,31 @@ import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw +class LedgerSignerFactory( + private val signingSharedState: MutableSharedState, + private val signFlowRequester: SignInterScreenRequester, + private val resourceManager: ResourceManager, + private val messageSigningNotSupported: SigningNotSupportedPresentable +) { + + fun create(metaAccount: MetaAccount): LedgerSigner { + return LedgerSigner( + metaAccount = metaAccount, + signingSharedState = signingSharedState, + signFlowRequester = signFlowRequester, + resourceManager = resourceManager, + messageSigningNotSupported = messageSigningNotSupported + ) + } +} + class LedgerSigner( + metaAccount: MetaAccount, signingSharedState: MutableSharedState, signFlowRequester: SignInterScreenRequester, private val resourceManager: ResourceManager, private val messageSigningNotSupported: SigningNotSupportedPresentable -) : SeparateFlowSigner(signingSharedState, signFlowRequester) { +) : SeparateFlowSigner(signingSharedState, signFlowRequester, metaAccount) { override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { messageSigningNotSupported.presentSigningNotSupported( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt index db0961074c..b2cb427b95 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_account_impl.data.signer.paritySigner import io.novafoundation.nova.common.base.errors.SigningCancelledException import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.formatWithPolkadotVaultLabel @@ -14,14 +15,46 @@ import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw +class PolkadotVaultVariantSignerFactory( + private val signingSharedState: MutableSharedState, + private val signFlowRequester: PolkadotVaultVariantSignCommunicator, + private val resourceManager: ResourceManager, + private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, + private val messageSigningNotSupported: SigningNotSupportedPresentable +) { + + fun createPolkadotVault(metaAccount: MetaAccount): PolkadotVaultSigner { + return PolkadotVaultSigner( + signingSharedState = signingSharedState, + metaAccount = metaAccount, + signFlowRequester = signFlowRequester, + resourceManager = resourceManager, + polkadotVaultVariantConfigProvider = polkadotVaultVariantConfigProvider, + messageSigningNotSupported = messageSigningNotSupported + ) + } + + fun createParitySigner(metaAccount: MetaAccount): ParitySignerSigner { + return ParitySignerSigner( + signingSharedState = signingSharedState, + metaAccount = metaAccount, + signFlowRequester = signFlowRequester, + resourceManager = resourceManager, + polkadotVaultVariantConfigProvider = polkadotVaultVariantConfigProvider, + messageSigningNotSupported = messageSigningNotSupported + ) + } +} + abstract class PolkadotVaultVariantSigner( signingSharedState: MutableSharedState, + metaAccount: MetaAccount, private val signFlowRequester: PolkadotVaultVariantSignCommunicator, private val resourceManager: ResourceManager, private val variant: PolkadotVaultVariant, private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, private val messageSigningNotSupported: SigningNotSupportedPresentable -) : SeparateFlowSigner(signingSharedState, signFlowRequester) { +) : SeparateFlowSigner(signingSharedState, signFlowRequester, metaAccount) { override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { signFlowRequester.setUsedVariant(variant) @@ -45,12 +78,14 @@ abstract class PolkadotVaultVariantSigner( class ParitySignerSigner( signingSharedState: MutableSharedState, + metaAccount: MetaAccount, signFlowRequester: PolkadotVaultVariantSignCommunicator, resourceManager: ResourceManager, polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, - messageSigningNotSupported: SigningNotSupportedPresentable + messageSigningNotSupported: SigningNotSupportedPresentable, ) : PolkadotVaultVariantSigner( signingSharedState = signingSharedState, + metaAccount = metaAccount, signFlowRequester = signFlowRequester, resourceManager = resourceManager, variant = PolkadotVaultVariant.PARITY_SIGNER, @@ -60,12 +95,14 @@ class ParitySignerSigner( class PolkadotVaultSigner( signingSharedState: MutableSharedState, + metaAccount: MetaAccount, signFlowRequester: PolkadotVaultVariantSignCommunicator, resourceManager: ResourceManager, polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, messageSigningNotSupported: SigningNotSupportedPresentable ) : PolkadotVaultVariantSigner( signingSharedState = signingSharedState, + metaAccount = metaAccount, signFlowRequester = signFlowRequester, resourceManager = resourceManager, variant = PolkadotVaultVariant.POLKADOT_VAULT, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt index 54ffa2c799..93ceb1bcfe 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt @@ -6,12 +6,14 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn -import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw +import java.math.BigInteger class ProxiedFeeSignerFactory( private val accountRepository: AccountRepository @@ -19,10 +21,10 @@ class ProxiedFeeSignerFactory( fun create(metaAccount: MetaAccount, chain: Chain, signerProvider: SignerProvider): ProxiedFeeSigner { return ProxiedFeeSigner( - metaAccount, - chain, - signerProvider, - accountRepository, + proxiedMetaAccount = metaAccount, + chain = chain, + signerProvider = signerProvider, + accountRepository = accountRepository, ) } } @@ -32,25 +34,26 @@ class ProxiedFeeSigner( private val chain: Chain, private val signerProvider: SignerProvider, private val accountRepository: AccountRepository, -) : FeeSigner { +) : NovaSigner { private var proxyMetaAccount: MetaAccount? = null - private var delegate: FeeSigner? = null + private var delegate: NovaSigner? = null override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { val delegator = getDelegator() val callInstance = payloadExtrinsic.call.toCallInstance() - if (callInstance == null) { - return delegator.signExtrinsic(payloadExtrinsic) + return if (callInstance == null) { + delegator.signExtrinsic(payloadExtrinsic) } else { - val modifienPayloadExtrinsic = payloadExtrinsic.wrapIntoProxyPayload( - getProxyAccountId(), - ProxyAccount.ProxyType.Any, - callInstance + val modifiedPayloadExtrinsic = payloadExtrinsic.wrapIntoProxyPayload( + proxyAccountId = getProxyAccountId(), + currentProxyNonce = BigInteger.ZERO, + proxyType = ProxyAccount.ProxyType.Any, + callInstance = callInstance ) - return delegator.signExtrinsic(modifienPayloadExtrinsic) + delegator.signExtrinsic(modifiedPayloadExtrinsic) } } @@ -58,13 +61,19 @@ class ProxiedFeeSigner( return getDelegator().signRaw(payload) } - override suspend fun accountId() = getDelegator().accountId() + override suspend fun signerAccountId(chain: Chain): AccountId { + require(chain.id == this.chain.id) { + "Signer was created for the different chain, expected ${this.chain.name}, got ${chain.name}" + } + + return getDelegator().signerAccountId(chain) + } private suspend fun getProxyAccountId(): ByteArray { return getProxyMetaAccount().requireAccountIdIn(chain) } - private suspend fun getDelegator(): FeeSigner { + private suspend fun getDelegator(): NovaSigner { if (delegate == null) { delegate = signerProvider.feeSigner(getProxyMetaAccount(), chain) } @@ -77,6 +86,6 @@ class ProxiedFeeSigner( proxyMetaAccount = accountRepository.getMetaAccount(proxiedMetaAccount.proxy!!.metaId) } - return proxyMetaAccount ?: throw IllegalStateException("Proxy meta account not found") + return requireNotNull(proxyMetaAccount) } } 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 c7ee95a352..d61fbbf1fa 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 @@ -1,7 +1,6 @@ package io.novafoundation.nova.feature_account_impl.data.signer.proxy import io.novafoundation.nova.common.base.errors.SigningCancelledException -import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 import io.novafoundation.nova.common.utils.chainId import io.novafoundation.nova.common.utils.toCallInstance import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository @@ -10,31 +9,35 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.feature_account_api.domain.model.requireAddressIn import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.network.rpc.RpcCalls +import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw class ProxiedSignerFactory( - private val secretStoreV2: SecretStoreV2, private val chainRegistry: ChainRegistry, private val accountRepository: AccountRepository, private val proxySigningPresenter: ProxySigningPresenter, - private val proxyRepository: ProxyRepository + private val proxyRepository: ProxyRepository, + private val rpcCalls: RpcCalls ) { fun create(metaAccount: MetaAccount, signerProvider: SignerProvider): ProxiedSigner { return ProxiedSigner( - metaAccount, - chainRegistry, - accountRepository, - signerProvider, - proxySigningPresenter, - proxyRepository + proxiedMetaAccount = metaAccount, + chainRegistry = chainRegistry, + accountRepository = accountRepository, + signerProvider = signerProvider, + proxySigningPresenter = proxySigningPresenter, + proxyRepository = proxyRepository, + rpcCalls = rpcCalls ) } } @@ -45,8 +48,16 @@ class ProxiedSigner( private val accountRepository: AccountRepository, private val signerProvider: SignerProvider, private val proxySigningPresenter: ProxySigningPresenter, - private val proxyRepository: ProxyRepository -) : Signer { + private val proxyRepository: ProxyRepository, + private val rpcCalls: RpcCalls, +) : NovaSigner { + + override suspend fun signerAccountId(chain: Chain): AccountId { + val proxyMetaAccount = getProxyMetaAccount() + val delegate = createDelegate(proxyMetaAccount) + + return delegate.signerAccountId(chain) + } override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { val proxyMetaAccount = getProxyMetaAccount() @@ -63,24 +74,38 @@ class ProxiedSigner( signingNotSupported() } - private suspend fun createDelegate(proxyMetaAccount: MetaAccount): Signer { + private fun createDelegate(proxyMetaAccount: MetaAccount): NovaSigner { return signerProvider.signerFor(proxyMetaAccount) } private suspend fun modifyPayload(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic): SignerPayloadExtrinsic { + val chain = chainRegistry.getChain(payload.chainId) + + val proxyAccountId = proxyMetaAccount.requireAccountIdIn(chain) + val proxiedAccountId = proxiedMetaAccount.requireAccountIdIn(chain) + val availableProxyTypes = proxyRepository.getDelegatedProxyTypes( - payload.chainId, - proxiedMetaAccount.getAccountId(payload.chainId), - proxyMetaAccount.getAccountId(payload.chainId) + chainId = payload.chainId, + proxiedAccountId = proxiedAccountId, + proxyAccountId = proxyAccountId ) val callInstance = payload.call.toCallInstance() ?: signingNotSupported() val module = callInstance.call.module + val proxyType = module.toProxyTypeMatcher() .matchToProxyTypes(availableProxyTypes) ?: notEnoughPermission(proxyMetaAccount, availableProxyTypes) - return payload.wrapIntoProxyPayload(proxyMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) + val proxyAddress = proxyMetaAccount.requireAddressIn(chain) + val nonce = rpcCalls.getNonce(payload.chainId, proxyAddress) + + return payload.wrapIntoProxyPayload( + proxyAccountId = proxyAccountId, + proxyType = proxyType, + callInstance = callInstance, + currentProxyNonce = nonce + ) } private suspend fun acknowledgeProxyOperation(proxyMetaAccount: MetaAccount) { @@ -90,11 +115,6 @@ class ProxiedSigner( } } - private suspend fun MetaAccount.getAccountId(chainId: ChainId): ByteArray { - val chain = chainRegistry.getChain(chainId) - return requireAccountIdIn(chain) - } - private suspend fun getProxyMetaAccount(): MetaAccount { val proxyAccount = proxiedMetaAccount.proxy ?: throw IllegalStateException("Proxy account is not found") return accountRepository.getMetaAccount(proxyAccount.metaId) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt index 39ce951fc4..beda9723c1 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt @@ -5,12 +5,16 @@ import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.DictEnum +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.DefaultSignedExtensions import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import jp.co.soramitsu.fearless_utils.runtime.definitions.types.instances.AddressInstanceConstructor +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.replaceBaseNone import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic +import java.math.BigInteger -suspend fun SignerPayloadExtrinsic.wrapIntoProxyPayload( +fun SignerPayloadExtrinsic.wrapIntoProxyPayload( proxyAccountId: AccountId, + currentProxyNonce: BigInteger, proxyType: ProxyAccount.ProxyType, callInstance: CallRepresentation.Instance ): SignerPayloadExtrinsic { @@ -26,5 +30,21 @@ suspend fun SignerPayloadExtrinsic.wrapIntoProxyPayload( ) ) - return copy(accountId = proxyAccountId, call = CallRepresentation.Instance(callBuilder.calls.first())) + val newNonce = nonce.replaceBaseNone(currentProxyNonce) + + return copy( + accountId = proxyAccountId, + call = CallRepresentation.Instance(callBuilder.calls.first()), + signedExtras = signedExtras.modifyNonce(newNonce.nonce), + nonce = newNonce + ) +} + + +fun Map.modifyNonce(newNonce: BigInteger): Map { + return buildMap { + putAll(this@modifyNonce) + + put(DefaultSignedExtensions.CHECK_NONCE, newNonce) + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/secrets/SecretsSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/secrets/SecretsSigner.kt index 200bc01919..e1b35e6d84 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/secrets/SecretsSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/secrets/SecretsSigner.kt @@ -8,6 +8,7 @@ import io.novafoundation.nova.common.sequrity.TwoFactorVerificationResult import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.multiChainEncryptionFor +import io.novafoundation.nova.feature_account_impl.data.signer.LeafSigner import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chainsById import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption @@ -15,7 +16,6 @@ import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.KeyPairSigner import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw @@ -26,7 +26,12 @@ class SecretsSignerFactory( ) { fun create(metaAccount: MetaAccount): SecretsSigner { - return SecretsSigner(metaAccount, secretStoreV2, chainRegistry, twoFactorVerificationService) + return SecretsSigner( + metaAccount = metaAccount, + secretStoreV2 = secretStoreV2, + chainRegistry = chainRegistry, + twoFactorVerificationService = twoFactorVerificationService + ) } } @@ -34,8 +39,8 @@ class SecretsSigner( private val metaAccount: MetaAccount, private val secretStoreV2: SecretStoreV2, private val chainRegistry: ChainRegistry, - private val twoFactorVerificationService: TwoFactorVerificationService -) : Signer { + private val twoFactorVerificationService: TwoFactorVerificationService, +) : LeafSigner(metaAccount) { override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { runTwoFactorVerificationIfEnabled() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/watchOnly/WatchOnlySigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/watchOnly/WatchOnlySigner.kt index 1d98b254cc..2bfac7055d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/watchOnly/WatchOnlySigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/watchOnly/WatchOnlySigner.kt @@ -1,16 +1,27 @@ package io.novafoundation.nova.feature_account_impl.data.signer.watchOnly import io.novafoundation.nova.common.base.errors.SigningCancelledException +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter +import io.novafoundation.nova.feature_account_impl.data.signer.LeafSigner import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw +class WatchOnlySignerFactory( + private val watchOnlySigningPresenter: WatchOnlyMissingKeysPresenter, +) { + + fun create(metaAccount: MetaAccount): WatchOnlySigner { + return WatchOnlySigner(watchOnlySigningPresenter, metaAccount) + } +} + class WatchOnlySigner( - private val watchOnlySigningPresenter: WatchOnlyMissingKeysPresenter -) : Signer { + private val watchOnlySigningPresenter: WatchOnlyMissingKeysPresenter, + metaAccount: MetaAccount +) : LeafSigner(metaAccount) { override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { cannotSign() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt index ca5106c618..06c6e8338a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt @@ -12,20 +12,20 @@ import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepositor import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider -import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_impl.data.signer.RealSignerProvider -import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner -import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.ParitySignerSigner -import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultSigner +import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator +import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory -import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySigner +import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySignerFactory import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.network.rpc.RpcCalls import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @Module @@ -46,12 +46,12 @@ class SignersModule { @Provides @FeatureScope fun provideProxiedSignerFactory( - secretStoreV2: SecretStoreV2, chainRegistry: ChainRegistry, accountRepository: AccountRepository, proxySigningPresenter: ProxySigningPresenter, proxyRepository: ProxyRepository, - ) = ProxiedSignerFactory(secretStoreV2, chainRegistry, accountRepository, proxySigningPresenter, proxyRepository) + rpcCalls: RpcCalls + ) = ProxiedSignerFactory(chainRegistry, accountRepository, proxySigningPresenter, proxyRepository, rpcCalls) @Provides @FeatureScope @@ -61,35 +61,19 @@ class SignersModule { @Provides @FeatureScope - fun provideWatchOnlySigner( + fun provideWatchOnlySignerFactory( watchOnlySigningPresenter: WatchOnlyMissingKeysPresenter - ) = WatchOnlySigner(watchOnlySigningPresenter) - - @Provides - @FeatureScope - fun provideParitySignerSigner( - signingSharedState: MutableSharedState, - communicator: PolkadotVaultVariantSignCommunicator, - resourceManager: ResourceManager, - polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, - signingNotSupportedPresentable: SigningNotSupportedPresentable - ) = ParitySignerSigner( - signingSharedState = signingSharedState, - signFlowRequester = communicator, - resourceManager = resourceManager, - polkadotVaultVariantConfigProvider = polkadotVaultVariantConfigProvider, - messageSigningNotSupported = signingNotSupportedPresentable - ) + ) = WatchOnlySignerFactory(watchOnlySigningPresenter) @Provides @FeatureScope - fun providePolkadotVaultSigner( + fun providePolkadotVaultVariantSignerFactory( signingSharedState: MutableSharedState, communicator: PolkadotVaultVariantSignCommunicator, resourceManager: ResourceManager, polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, signingNotSupportedPresentable: SigningNotSupportedPresentable - ) = PolkadotVaultSigner( + ) = PolkadotVaultVariantSignerFactory( signingSharedState = signingSharedState, signFlowRequester = communicator, resourceManager = resourceManager, @@ -99,30 +83,28 @@ class SignersModule { @Provides @FeatureScope - fun provideLedgerSigner( + fun provideLedgerSignerFactory( signingSharedState: MutableSharedState, communicator: LedgerSignCommunicator, resourceManager: ResourceManager, signingNotSupportedPresentable: SigningNotSupportedPresentable - ) = LedgerSigner(signingSharedState, communicator, resourceManager, signingNotSupportedPresentable) + ) = LedgerSignerFactory(signingSharedState, communicator, resourceManager, signingNotSupportedPresentable) @Provides @FeatureScope fun provideSignerProvider( secretsSignerFactory: SecretsSignerFactory, proxiedSignerFactory: ProxiedSignerFactory, - watchOnlySigner: WatchOnlySigner, - paritySignerSigner: ParitySignerSigner, - polkadotVaultSigner: PolkadotVaultSigner, + watchOnlySignerFactory: WatchOnlySignerFactory, + polkadotVaultSignerFactory: PolkadotVaultVariantSignerFactory, proxiedFeeSignerFactory: ProxiedFeeSignerFactory, - ledgerSigner: LedgerSigner + ledgerSignerFactory: LedgerSignerFactory ): SignerProvider = RealSignerProvider( secretsSignerFactory = secretsSignerFactory, - watchOnlySigner = watchOnlySigner, - paritySignerSigner = paritySignerSigner, - polkadotVaultSigner = polkadotVaultSigner, + watchOnlySigner = watchOnlySignerFactory, + polkadotVaultSignerFactory = polkadotVaultSignerFactory, proxiedSignerFactory = proxiedSignerFactory, proxiedFeeSignerFactory = proxiedFeeSignerFactory, - ledgerSigner = ledgerSigner + ledgerSignerFactory = ledgerSignerFactory ) } diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/ExternalSignFeatureDependencies.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/ExternalSignFeatureDependencies.kt index cc6c9bea3a..b02246d59c 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/ExternalSignFeatureDependencies.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/di/ExternalSignFeatureDependencies.kt @@ -19,6 +19,7 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoade import io.novafoundation.nova.runtime.di.ExtrinsicSerialization import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.network.rpc.RpcCalls import okhttp3.OkHttpClient interface ExternalSignFeatureDependencies { @@ -61,4 +62,6 @@ interface ExternalSignFeatureDependencies { val signerProvider: SignerProvider val gasPriceProviderFactory: GasPriceProviderFactory + + val rpcCalls: RpcCalls } diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/BaseExternalSignInteractor.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/BaseExternalSignInteractor.kt index 2934dbe26d..89d6e08022 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/BaseExternalSignInteractor.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/BaseExternalSignInteractor.kt @@ -4,7 +4,7 @@ import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_external_sign_api.model.signPayload.ExternalSignWallet -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner abstract class BaseExternalSignInteractor( private val accountRepository: AccountRepository, @@ -12,7 +12,7 @@ abstract class BaseExternalSignInteractor( private val signerProvider: SignerProvider, ) : ExternalSignInteractor { - protected suspend fun resolveWalletSigner(): Signer { + protected suspend fun resolveWalletSigner(): NovaSigner { val metaAccount = resolveMetaAccount() return signerProvider.signerFor(metaAccount) 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 840cb92a7b..86e2ba1824 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 @@ -39,6 +39,7 @@ import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.EraType import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.Nonce import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.fromHex import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.fromUtf8 @@ -208,7 +209,7 @@ class PolkadotExternalSignInteractor( return with(parsedExtrinsic) { ExtrinsicBuilder( runtime = runtime, - nonce = nonce, + nonce = Nonce.singleTx(nonce), runtimeVersion = RuntimeVersion( specVersion = specVersion, transactionVersion = transactionVersion 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 838569bddf..21009fb9a0 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 @@ -4,7 +4,7 @@ import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.core_db.dao.ChainDao import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.ext.requireGenesisHash -import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.mapper.toRuntimeVersion import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -13,8 +13,10 @@ import io.novafoundation.nova.runtime.network.rpc.RpcCalls import jp.co.soramitsu.fearless_utils.extensions.fromHex import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.Nonce import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import jp.co.soramitsu.fearless_utils.wsrpc.request.runtime.chain.RuntimeVersion +import java.math.BigInteger class ExtrinsicBuilderFactory( private val chainDao: ChainDao, @@ -27,7 +29,7 @@ class ExtrinsicBuilderFactory( * Create with special signer for fee calculation */ suspend fun createForFee( - signer: FeeSigner, + signer: NovaSigner, chain: Chain, ): ExtrinsicBuilder { return createMultiForFee(signer, chain).first() @@ -45,10 +47,10 @@ class ExtrinsicBuilderFactory( } suspend fun createMultiForFee( - signer: FeeSigner, + signer: NovaSigner, chain: Chain, ): Sequence { - return createMulti(chain, signer, signer.accountId()) + return createMulti(chain, signer, signer.signerAccountId(chain)) } suspend fun createMulti( @@ -63,13 +65,14 @@ class ExtrinsicBuilderFactory( val runtimeVersion = getRuntimeVersion(chain) val mortality = mortalityConstructor.constructMortality(chain.id) - var nonce = rpcCalls.getNonce(chain.id, accountAddress) + val baseNonce = rpcCalls.getNonce(chain.id, accountAddress) + var nonceOffset = BigInteger.ZERO return generateSequence { val newElement = ExtrinsicBuilder( tip = chain.additional?.defaultTip.orZero(), runtime = runtime, - nonce = nonce, + nonce = Nonce(baseNonce, nonceOffset), runtimeVersion = runtimeVersion, genesisHash = chain.requireGenesisHash().fromHex(), blockHash = mortality.blockHash.fromHex(), @@ -79,7 +82,7 @@ class ExtrinsicBuilderFactory( accountId = accountId ) - nonce++ + nonceOffset++ newElement } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt deleted file mode 100644 index 381fcec30b..0000000000 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/FeeSigner.kt +++ /dev/null @@ -1,9 +0,0 @@ -package io.novafoundation.nova.runtime.extrinsic.feeSigner - -import jp.co.soramitsu.fearless_utils.runtime.AccountId -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer - -interface FeeSigner : Signer { - - suspend fun accountId(): AccountId -} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt index a8f689cd96..997480d0e1 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/multi/ExtrinsicSplitter.kt @@ -5,7 +5,7 @@ import io.novafoundation.nova.common.data.network.runtime.binding.Weight import io.novafoundation.nova.common.utils.times import io.novafoundation.nova.runtime.ext.requireGenesisHash import io.novafoundation.nova.runtime.extrinsic.CustomSignedExtensions -import io.novafoundation.nova.runtime.extrinsic.feeSigner.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.network.rpc.RpcCalls import io.novafoundation.nova.runtime.repository.BlockLimitsRepository @@ -14,6 +14,7 @@ import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Era import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.Nonce import jp.co.soramitsu.fearless_utils.wsrpc.request.runtime.chain.RuntimeVersion import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred @@ -24,7 +25,7 @@ typealias SplitCalls = List> interface ExtrinsicSplitter { - suspend fun split(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls + suspend fun split(signer: NovaSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls } private typealias CallWeightsByType = Map> @@ -36,7 +37,7 @@ internal class RealExtrinsicSplitter( private val blockLimitsRepository: BlockLimitsRepository, ) : ExtrinsicSplitter { - override suspend fun split(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls = coroutineScope { + override suspend fun split(signer: NovaSigner, callBuilder: CallBuilder, chain: Chain): SplitCalls = coroutineScope { val weightByCallId = estimateWeightByCallType(signer, callBuilder, chain) val blockLimit = blockLimitsRepository.maxWeightForNormalExtrinsics(chain.id) * LEAVE_SOME_SPACE_MULTIPLIER @@ -52,7 +53,7 @@ internal class RealExtrinsicSplitter( } @Suppress("SuspendFunctionOnCoroutineScope") - private suspend fun CoroutineScope.estimateWeightByCallType(signer: FeeSigner, callBuilder: CallBuilder, chain: Chain): CallWeightsByType { + private suspend fun CoroutineScope.estimateWeightByCallType(signer: NovaSigner, callBuilder: CallBuilder, chain: Chain): CallWeightsByType { return callBuilder.calls.groupBy { it.uniqueId } .mapValues { (_, calls) -> val sample = calls.first() @@ -91,20 +92,20 @@ internal class RealExtrinsicSplitter( return split } - private suspend fun wrapInFakeExtrinsic(signer: FeeSigner, call: GenericCall.Instance, runtime: RuntimeSnapshot, chain: Chain): String { + private suspend fun wrapInFakeExtrinsic(signer: NovaSigner, call: GenericCall.Instance, runtime: RuntimeSnapshot, chain: Chain): String { val genesisHash = chain.requireGenesisHash().fromHex() return ExtrinsicBuilder( tip = BalanceOf.ZERO, runtime = runtime, - nonce = BalanceOf.ZERO, + nonce = Nonce.zero(), runtimeVersion = RuntimeVersion(specVersion = 0, transactionVersion = 0), genesisHash = genesisHash, blockHash = genesisHash, era = Era.Immortal, customSignedExtensions = CustomSignedExtensions.extensionsWithValues(), signer = signer, - accountId = signer.accountId() + accountId = signer.signerAccountId(chain) ) .call(call) .build() diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/DefaultFeeSigner.kt similarity index 83% rename from runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt rename to runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/DefaultFeeSigner.kt index 47c9060e62..c43a0f1181 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/feeSigner/DefaultFeeSigner.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/DefaultFeeSigner.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.runtime.extrinsic.feeSigner +package io.novafoundation.nova.runtime.extrinsic.signer import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -15,7 +15,7 @@ import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw private val FAKE_CRYPTO_TYPE = EncryptionType.ECDSA -class DefaultFeeSigner(private val chain: Chain) : FeeSigner { +class DefaultFeeSigner(private val chain: Chain) : NovaSigner { private val keypair = generateFakeKeyPair() @@ -31,7 +31,13 @@ class DefaultFeeSigner(private val chain: Chain) : FeeSigner { return signer.signRaw(payload) } - override suspend fun accountId() = chain.accountIdOf(keypair.publicKey) + override suspend fun signerAccountId(chain: Chain): ByteArray { + require(chain.id == this.chain.id) { + "Signer was created for the different chain, expected ${this.chain.name}, got ${chain.name}" + } + + return chain.accountIdOf(keypair.publicKey) + } private fun multiChainEncryption() = if (chain.isEthereumBased) { MultiChainEncryption.Ethereum diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/NovaSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/NovaSigner.kt new file mode 100644 index 0000000000..1b05ab4c5f --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/NovaSigner.kt @@ -0,0 +1,10 @@ +package io.novafoundation.nova.runtime.extrinsic.signer + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer + +interface NovaSigner : Signer { + + suspend fun signerAccountId(chain: Chain): AccountId +} From 83ee55a61188579941aeb2ef296c8361b6dbbbf6 Mon Sep 17 00:00:00 2001 From: Valentun Date: Fri, 15 Dec 2023 15:36:07 +0300 Subject: [PATCH 040/100] Fix - exotic nonce structures --- .../data/signer/proxy/SignerPayloadModifierExt.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt index beda9723c1..d6246ee572 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt @@ -4,10 +4,12 @@ import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.DictEnum import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.DefaultSignedExtensions import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import jp.co.soramitsu.fearless_utils.runtime.definitions.types.instances.AddressInstanceConstructor +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.encodeNonce import jp.co.soramitsu.fearless_utils.runtime.extrinsic.replaceBaseNone import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import java.math.BigInteger @@ -35,16 +37,16 @@ fun SignerPayloadExtrinsic.wrapIntoProxyPayload( return copy( accountId = proxyAccountId, call = CallRepresentation.Instance(callBuilder.calls.first()), - signedExtras = signedExtras.modifyNonce(newNonce.nonce), + signedExtras = signedExtras.modifyNonce(runtime, newNonce.nonce), nonce = newNonce ) } -fun Map.modifyNonce(newNonce: BigInteger): Map { +fun Map.modifyNonce(runtimeSnapshot: RuntimeSnapshot, newNonce: BigInteger): Map { return buildMap { putAll(this@modifyNonce) - put(DefaultSignedExtensions.CHECK_NONCE, newNonce) + put(DefaultSignedExtensions.CHECK_NONCE, runtimeSnapshot.encodeNonce(newNonce)) } } From 2c6994f90b56fc5eca6d08c6177a599fb4c94160 Mon Sep 17 00:00:00 2001 From: Valentun Date: Fri, 15 Dec 2023 15:39:43 +0300 Subject: [PATCH 041/100] Code style --- .../nova/feature_account_impl/data/signer/LeafSigner.kt | 2 +- .../data/signer/proxy/SignerPayloadModifierExt.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt index 5c4d483d47..0cc896cc2c 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt @@ -8,7 +8,7 @@ import jp.co.soramitsu.fearless_utils.runtime.AccountId abstract class LeafSigner( private val metaAccount: MetaAccount, -): NovaSigner { +) : NovaSigner { override suspend fun signerAccountId(chain: Chain): AccountId { return metaAccount.requireAccountIdIn(chain) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt index d6246ee572..12ede274f2 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt @@ -42,7 +42,6 @@ fun SignerPayloadExtrinsic.wrapIntoProxyPayload( ) } - fun Map.modifyNonce(runtimeSnapshot: RuntimeSnapshot, newNonce: BigInteger): Map { return buildMap { putAll(this@modifyNonce) From 9da43ab2688c0085c0d1f7852e6093af7f9c7f3e Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Fri, 15 Dec 2023 15:45:54 +0300 Subject: [PATCH 042/100] Fix/staking paged exposures (#1267) * Paged exposures * Payouts * Fixes * Fixes * Update tags * Code style * Remove unused class * Remove unused logs * Fix tests --- .../nova/NftUniquesIntegrationTest.kt | 2 +- .../network/runtime/calls/GetStorageSize.kt | 8 + .../subquery/EraValidatorInfoQueryResponse.kt | 20 +- .../nova/common/utils/KotlinExt.kt | 43 ++ .../nova/common/utils/PartitionTest.kt | 27 + .../ethereum/transaction/TransactionOrigin.kt | 4 + .../data/extrinsic/ExtrinsicService.kt | 6 +- .../domain/interfaces/AccountRepositoryExt.kt | 9 + .../transaction/RealEvmTransactionService.kt | 13 +- .../data/extrinsic/RealExtrinsicService.kt | 17 +- .../NewDelegationChooseAmountInteractor.kt | 3 +- ...RealNewDelegationChooseAmountInteractor.kt | 6 +- .../revoke/RevokeDelegationsInteractor.kt | 9 +- .../NewDelegationChooseAmountViewModel.kt | 7 +- .../RevokeDelegationConfirmViewModel.kt | 2 +- .../providers/uniques/UniquesNftProvider.kt | 61 +- .../domain/model/Exposure.kt | 9 + .../feature_staking_impl/data/model/Payout.kt | 6 +- .../blockhain/bindings/ClaimedRewardPages.kt | 15 + .../blockhain/bindings/EraRewardPoints.kt | 20 +- .../network/blockhain/bindings/Exposure.kt | 46 +- .../blockhain/bindings/StakingLedger.kt | 18 +- .../blockhain/bindings/ValidatorPrefs.kt | 14 - .../blockhain/calls/ExtrinsicBuilderExt.kt | 11 - .../data/network/blockhain/updaters/Common.kt | 21 +- .../updaters/ValidatorExposureUpdater.kt | 183 +++++- .../HistoricalTotalValidatorRewardUpdater.kt | 5 - .../historical/HistoricalUpdateMediator.kt | 45 +- .../HistoricalValidatorRewardPointsUpdater.kt | 5 - .../data/network/subquery/StakingApi.kt | 13 +- .../subquery/SubQueryValidatorSetFetcher.kt | 72 ++- ....kt => StakingNominatorEraInfosRequest.kt} | 6 +- .../StakingValidatorEraInfosRequest.kt | 25 + .../data/repository/PayoutRepository.kt | 544 +++++++++++++----- .../repository/StakingConstantsRepository.kt | 7 +- .../data/repository/StakingRepositoryImpl.kt | 77 ++- .../di/StakingFeatureDependencies.kt | 3 + .../di/StakingFeatureModule.kt | 19 +- .../RelaychainStakingUpdatersModule.kt | 13 +- .../domain/StakingInteractor.kt | 38 +- .../domain/StakingInteractorExt.kt | 7 +- .../domain/alerts/AlertsInteractor.kt | 2 +- .../domain/common/StakingSharedComputation.kt | 2 - .../domain/model/PendingPayoutsStatistics.kt | 1 + .../domain/payout/PayoutInteractor.kt | 67 ++- .../RecommendationSettingsProvider.kt | 3 +- .../RecommendationSettingsProviderFactory.kt | 7 +- .../filters/NotOverSubscribedFilter.kt | 14 +- .../validations/payout/MakePayoutPayload.kt | 8 +- .../domain/validators/ValidatorProvider.kt | 2 +- .../current/CurrentValidatorsInteractor.kt | 2 +- .../presentation/mappers/Validator.kt | 4 +- .../payouts/confirm/ConfirmPayoutFragment.kt | 1 + .../payouts/confirm/ConfirmPayoutViewModel.kt | 39 +- .../payouts/confirm/di/ConfirmPayoutModule.kt | 5 +- .../payouts/list/PayoutsListViewModel.kt | 13 +- .../payouts/model/PendingPayoutParcelable.kt | 15 + .../details/StakeTargetDetailsPayload.kt | 2 +- .../mixin/amountChooser/AmountChooserMixin.kt | 3 +- .../presentation/mixin/fee/FeeLoaderMixin.kt | 25 +- .../nova/runtime/network/rpc/RpcCalls.kt | 7 + .../updaters/SingleChainUpdateSystem.kt | 2 +- .../storage/source/multi/MultiQueryBuilder.kt | 33 +- .../source/multi/MultiQueryBuilderImpl.kt | 64 ++- .../source/query/BaseStorageQueryContext.kt | 28 +- .../source/query/StorageQueryContext.kt | 22 +- 66 files changed, 1305 insertions(+), 525 deletions(-) create mode 100644 common/src/main/java/io/novafoundation/nova/common/data/network/runtime/calls/GetStorageSize.kt create mode 100644 common/src/test/java/io/novafoundation/nova/common/utils/PartitionTest.kt create mode 100644 feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/ClaimedRewardPages.kt rename feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/{StakingEraValidatorInfosRequest.kt => StakingNominatorEraInfosRequest.kt} (75%) create mode 100644 feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingValidatorEraInfosRequest.kt diff --git a/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt index c8bf0c8687..59c3bff422 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt @@ -93,7 +93,7 @@ class NftUniquesIntegrationTest { val instanceMetadataStorage = runtime.metadata.uniques().storage("InstanceMetadataOf") val instanceDetailsStorage = runtime.metadata.uniques().storage("Asset") - val multiQueryResults = multi { + val multiQueryResults = multiInternal { classMetadataStorage.querySingleArgKeys(classesIds) classStorage.querySingleArgKeys(classesIds) instanceMetadataStorage.queryKeys(classesWithInstances) diff --git a/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/calls/GetStorageSize.kt b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/calls/GetStorageSize.kt new file mode 100644 index 0000000000..aaec3d2007 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/calls/GetStorageSize.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.common.data.network.runtime.calls + +import jp.co.soramitsu.fearless_utils.wsrpc.request.runtime.RuntimeRequest + +class GetStorageSize(key: String) : RuntimeRequest( + method = "state_getStorageSize", + params = listOfNotNull(key) +) diff --git a/common/src/main/java/io/novafoundation/nova/common/data/network/subquery/EraValidatorInfoQueryResponse.kt b/common/src/main/java/io/novafoundation/nova/common/data/network/subquery/EraValidatorInfoQueryResponse.kt index 2685338783..1f853f5362 100644 --- a/common/src/main/java/io/novafoundation/nova/common/data/network/subquery/EraValidatorInfoQueryResponse.kt +++ b/common/src/main/java/io/novafoundation/nova/common/data/network/subquery/EraValidatorInfoQueryResponse.kt @@ -2,16 +2,12 @@ package io.novafoundation.nova.common.data.network.subquery import java.math.BigInteger -class EraValidatorInfoQueryResponse(val query: EraValidatorInfo?) { - class EraValidatorInfo(val eraValidatorInfos: Nodes?) { - class Nodes(val nodes: List?) { - class Node( - val id: String, - val address: String, - val era: BigInteger, - val total: String, - val own: String, - ) - } - } +class EraValidatorInfoQueryResponse(val eraValidatorInfos: SubQueryNodes?) { + class EraValidatorInfo( + val id: String, + val address: String, + val era: BigInteger, + val total: String, + val own: String, + ) } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt index daa8706e17..6484d38314 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/KotlinExt.kt @@ -170,6 +170,47 @@ fun Result.requireException() = exceptionOrNull()!! fun Result.requireValue() = getOrThrow()!! +/** + * Given a list finds a partition point in O(log2(N)) given that there is only a single partition point present. + * That is, there is only a single place in the whole array where the value of [partition] changes from false to true + * + * @return index of the first element where invocation of [partition] returns true. Returns null in case there is no such elements in the list + */ +inline fun List.findPartitionPoint(partition: (T) -> Boolean): Int? { + if (isEmpty()) return null + + var lowIdx = 0 + var highIdx = size - 1 + + while (highIdx - lowIdx > 1) { + val midIdx = (lowIdx + highIdx) / 2 + + val midValue = get(midIdx) + val isPartitionTrue = partition(midValue) + + if (isPartitionTrue) { + highIdx = midIdx + } else { + lowIdx = midIdx + 1 + } + } + + val isLowTrue = partition(get(lowIdx)) + if (isLowTrue) return lowIdx + + val isHighTrue = partition(get(highIdx)) + if (isHighTrue) return highIdx + + return null +} + +/** + * @see [findPartitionPoint] + */ +fun List.findPartitionPoint(): Int? { + return findPartitionPoint { it } +} + fun Result.mapFailure(transform: (Throwable) -> Throwable): Result { return when { isFailure -> Result.failure(transform(requireException())) @@ -349,6 +390,8 @@ inline fun Iterable.mapNotNullToSet(mapper: (T) -> R?): Set = fun List.indexOfFirstOrNull(predicate: (T) -> Boolean) = indexOfFirst(predicate).takeIf { it >= 0 } +fun List.indexOfOrNull(value: T) = indexOf(value).takeIf { it >= 0 } + @Suppress("IfThenToElvis") fun ByteArray?.optionalContentEquals(other: ByteArray?): Boolean { return if (this == null) { diff --git a/common/src/test/java/io/novafoundation/nova/common/utils/PartitionTest.kt b/common/src/test/java/io/novafoundation/nova/common/utils/PartitionTest.kt new file mode 100644 index 0000000000..c945ef7d98 --- /dev/null +++ b/common/src/test/java/io/novafoundation/nova/common/utils/PartitionTest.kt @@ -0,0 +1,27 @@ +package io.novafoundation.nova.common.utils + +import org.junit.Assert.assertEquals +import org.junit.Test + +internal class PartitionTest { + + @Test + fun testEveryCombinationBelowSize100() { + (1..100).map { size -> + (0 .. size).map { truePointIndex -> + val list = List(truePointIndex) { false } + List(size - truePointIndex) { true } + runTest(list, expectedResult = truePointIndex.takeIf { truePointIndex < size }) + } + } + } + + private fun runTest( + list: List, + expectedResult: Int? + ) { + var iterationCount = 0 + val actualResult = list.findPartitionPoint { iterationCount++; it } + + assertEquals("Expected: ${expectedResult}, Got: $actualResult in $list", expectedResult, actualResult) + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt index 8c03dd2b52..0396e87200 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt @@ -1,6 +1,10 @@ package io.novafoundation.nova.feature_account_api.data.ethereum.transaction +import jp.co.soramitsu.fearless_utils.runtime.AccountId + sealed class TransactionOrigin { object SelectedWallet : TransactionOrigin() + + class WalletWithAccount(val accountId: AccountId) : TransactionOrigin() } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt index 6077cb208d..cb5ce4b165 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_account_api.data.extrinsic import io.novafoundation.nova.common.data.network.runtime.model.FeeResponse import io.novafoundation.nova.common.utils.multiResult.RetriableMultiResult +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder @@ -22,8 +23,9 @@ class ExtrinsicSubmission(val hash: String, val origin: AccountId) interface ExtrinsicService { - suspend fun submitMultiExtrinsicWithSelectedWalletAwaitingInclusion( + suspend fun submitMultiExtrinsicAwaitingInclusion( chain: Chain, + origin: TransactionOrigin, formExtrinsic: FormMultiExtrinsicWithOrigin, ): RetriableMultiResult @@ -75,7 +77,7 @@ interface ExtrinsicService { suspend fun estimateMultiFee( chain: Chain, formExtrinsic: FormMultiExtrinsic, - ): BigInteger + ): Fee suspend fun estimateFee(chain: Chain, extrinsic: String): Fee } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepositoryExt.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepositoryExt.kt index 09f0001dd7..2413766312 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepositoryExt.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepositoryExt.kt @@ -1,5 +1,7 @@ package io.novafoundation.nova.feature_account_api.domain.interfaces +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -20,3 +22,10 @@ suspend fun AccountRepository.getIdOfSelectedMetaAccountIn(chain: Chain): Accoun return metaAccount.accountIdIn(chain) } + +suspend fun AccountRepository.requireMetaAccountFor(transactionOrigin: TransactionOrigin): MetaAccount { + return when (transactionOrigin) { + TransactionOrigin.SelectedWallet -> getSelectedMetaAccount() + is TransactionOrigin.WalletWithAccount -> findMetaAccountOrThrow(transactionOrigin.accountId) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index 06111aee8d..742d626c82 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.feature_account_api.data.model.EvmFee import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.interfaces.requireMetaAccountFor import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAddressIn @@ -18,9 +19,9 @@ import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.runtime.ethereum.sendSuspend import io.novafoundation.nova.runtime.ethereum.transaction.builder.EvmTransactionBuilder import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw @@ -49,7 +50,7 @@ internal class RealEvmTransactionService( val web3Api = chainRegistry.getCallEthereumApiOrThrow(chainId) val chain = chainRegistry.getChain(chainId) - val submittingMetaAccount = findMetaAccountFor(origin) + val submittingMetaAccount = accountRepository.requireMetaAccountFor(origin) val submittingAddress = submittingMetaAccount.requireAddressIn(chain) val txBuilder = EvmTransactionBuilder().apply(building) @@ -69,7 +70,7 @@ internal class RealEvmTransactionService( building: EvmTransactionBuilding ): Result = runCatching { val chain = chainRegistry.getChain(chainId) - val submittingMetaAccount = findMetaAccountFor(origin) + val submittingMetaAccount = accountRepository.requireMetaAccountFor(origin) val submittingAddress = submittingMetaAccount.requireAddressIn(chain) val web3Api = chainRegistry.getCallEthereumApiOrThrow(chainId) @@ -106,12 +107,6 @@ internal class RealEvmTransactionService( return txForSign.encodeWith(eip155SignatureData).toHexString(withPrefix = true) } - private suspend fun findMetaAccountFor(origin: TransactionOrigin): MetaAccount { - return when (origin) { - TransactionOrigin.SelectedWallet -> accountRepository.getSelectedMetaAccount() - } - } - private suspend fun Web3Api.getNonce(address: String): BigInteger { return ethGetTransactionCount(address, DefaultBlockParameterName.PENDING) .sendSuspend() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index 5b13960930..85124c6391 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.common.utils.sum import io.novafoundation.nova.common.utils.takeWhileInclusive import io.novafoundation.nova.common.utils.tip +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.extrinsic.FormExtrinsicWithOrigin @@ -16,6 +17,7 @@ import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.data.model.InlineFee import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.interfaces.requireMetaAccountFor import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory @@ -45,12 +47,13 @@ class RealExtrinsicService( private val extrinsicSplitter: ExtrinsicSplitter, ) : ExtrinsicService { - override suspend fun submitMultiExtrinsicWithSelectedWalletAwaitingInclusion( + override suspend fun submitMultiExtrinsicAwaitingInclusion( chain: Chain, + origin: TransactionOrigin, formExtrinsic: FormMultiExtrinsicWithOrigin ): RetriableMultiResult { return runMultiCatching( - intermediateListLoading = { constructSplitExtrinsicsForSubmission(chain, formExtrinsic) }, + intermediateListLoading = { constructSplitExtrinsicsForSubmission(chain, formExtrinsic, origin) }, listProcessing = { extrinsic -> rpcCalls.submitAndWatchExtrinsic(chain.id, extrinsic) .filterIsInstance() @@ -146,21 +149,23 @@ class RealExtrinsicService( return InlineFee(tip + baseFee) } - override suspend fun estimateMultiFee(chain: Chain, formExtrinsic: FormMultiExtrinsic): BigInteger { + override suspend fun estimateMultiFee(chain: Chain, formExtrinsic: FormMultiExtrinsic): Fee { val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(chain) val extrinsics = constructSplitExtrinsics(chain, formExtrinsic, feeExtrinsicBuilderSequence) val separateFees = extrinsics.map { estimateFee(chain, it).amount } + val totalFee = separateFees.sum() - return separateFees.sum() + return InlineFee(totalFee) } private suspend fun constructSplitExtrinsicsForSubmission( chain: Chain, - formExtrinsic: FormMultiExtrinsicWithOrigin + formExtrinsic: FormMultiExtrinsicWithOrigin, + origin: TransactionOrigin, ): List { - val metaAccount = accountRepository.getSelectedMetaAccount() + val metaAccount = accountRepository.requireMetaAccountFor(origin) val signer = signerProvider.signerFor(metaAccount) val accountId = metaAccount.requireAccountIdIn(chain) diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountInteractor.kt index 43fd41feba..d8d4b1dacf 100644 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountInteractor.kt +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountInteractor.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_governance_api.domain.delegation.delegation.create.chooseAmount import io.novafoundation.nova.common.utils.multiResult.RetriableMultiResult +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus @@ -21,7 +22,7 @@ interface NewDelegationChooseAmountInteractor { delegate: AccountId, tracks: Collection, shouldRemoveOtherTracks: Boolean, - ): Balance + ): Fee suspend fun delegate( amount: Balance, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealNewDelegationChooseAmountInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealNewDelegationChooseAmountInteractor.kt index 2ba1c51886..eae1164b8e 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealNewDelegationChooseAmountInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealNewDelegationChooseAmountInteractor.kt @@ -2,7 +2,9 @@ package io.novafoundation.nova.feature_governance_impl.domain.delegation.delegat import io.novafoundation.nova.common.data.memory.ComputationalCache import io.novafoundation.nova.common.utils.multiResult.RetriableMultiResult +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.requireIdOfSelectedMetaAccountIn import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId @@ -54,7 +56,7 @@ class RealNewDelegationChooseAmountInteractor( delegate: AccountId, tracks: Collection, shouldRemoveOtherTracks: Boolean, - ): Balance { + ): Fee { val (chain, governanceSource) = useSelectedGovernance() val origin = accountRepository.requireIdOfSelectedMetaAccountIn(chain) @@ -72,7 +74,7 @@ class RealNewDelegationChooseAmountInteractor( ): RetriableMultiResult { val (chain, governanceSource) = useSelectedGovernance() - return extrinsicService.submitMultiExtrinsicWithSelectedWalletAwaitingInclusion(chain) { origin -> + return extrinsicService.submitMultiExtrinsicAwaitingInclusion(chain, TransactionOrigin.SelectedWallet) { origin -> delegate(governanceSource, amount, conviction, delegate, origin, chain, tracks, shouldRemoveOtherTracks) } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/RevokeDelegationsInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/RevokeDelegationsInteractor.kt index 80c72e72bd..7c40b48081 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/RevokeDelegationsInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/RevokeDelegationsInteractor.kt @@ -3,7 +3,9 @@ package io.novafoundation.nova.feature_governance_impl.domain.delegation.delegat import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.common.utils.multiResult.RetriableMultiResult import io.novafoundation.nova.common.utils.orZero +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.requireIdOfSelectedMetaAccountIn import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId @@ -16,7 +18,6 @@ import io.novafoundation.nova.feature_governance_api.domain.track.matchWith import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState import io.novafoundation.nova.feature_governance_impl.domain.track.TracksUseCase import io.novafoundation.nova.feature_governance_impl.domain.track.tracksByIdOf -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -27,7 +28,7 @@ import kotlinx.coroutines.flow.Flow interface RevokeDelegationsInteractor { - suspend fun calculateFee(trackIds: Collection): Balance + suspend fun calculateFee(trackIds: Collection): Fee suspend fun revokeDelegations(trackIds: Collection): RetriableMultiResult @@ -43,7 +44,7 @@ class RealRevokeDelegationsInteractor( private val tracksUseCase: TracksUseCase, ) : RevokeDelegationsInteractor { - override suspend fun calculateFee(trackIds: Collection): Balance { + override suspend fun calculateFee(trackIds: Collection): Fee { val (chain, source) = useSelectedGovernance() return extrinsicService.estimateMultiFee(chain) { @@ -54,7 +55,7 @@ class RealRevokeDelegationsInteractor( override suspend fun revokeDelegations(trackIds: Collection): RetriableMultiResult { val (chain, source) = useSelectedGovernance() - return extrinsicService.submitMultiExtrinsicWithSelectedWalletAwaitingInclusion(chain) { + return extrinsicService.submitMultiExtrinsicAwaitingInclusion(chain, TransactionOrigin.SelectedWallet) { revokeDelegations(source, trackIds) } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt index 4e7dae9a53..9b696c38e7 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt @@ -31,7 +31,8 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChoose import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.setAmountInput import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWithV2 import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -126,7 +127,7 @@ class NewDelegationChooseAmountViewModel( } init { - originFeeMixin.connectWith( + originFeeMixin.connectWithV2( inputSource1 = amountChooserMixin.backPressuredAmount, inputSource2 = selectedConvictionFlow, scope = this, @@ -156,7 +157,7 @@ class NewDelegationChooseAmountViewModel( private fun openConfirmIfValid() = launch { validationInProgressFlow.value = true - val fee = originFeeMixin.awaitFee() + val fee = originFeeMixin.awaitDecimalFee().networkFeeDecimalAmount val payload = ChooseDelegationAmountValidationPayload( asset = selectedAsset.first(), fee = fee, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt index b0ec2677ac..65d6904e0b 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt @@ -166,7 +166,7 @@ class RevokeDelegationConfirmViewModel( } private fun loadFee() = launch { - originFeeMixin.loadFee( + originFeeMixin.loadFeeV2( coroutineScope = coroutineScope, feeConstructor = { interactor.calculateFee(payload.trackIds) }, onRetryCancelled = {} diff --git a/feature-nft-impl/src/main/java/io/novafoundation/nova/feature_nft_impl/data/source/providers/uniques/UniquesNftProvider.kt b/feature-nft-impl/src/main/java/io/novafoundation/nova/feature_nft_impl/data/source/providers/uniques/UniquesNftProvider.kt index 7db9ad91e2..c382e92c9e 100644 --- a/feature-nft-impl/src/main/java/io/novafoundation/nova/feature_nft_impl/data/source/providers/uniques/UniquesNftProvider.kt +++ b/feature-nft-impl/src/main/java/io/novafoundation/nova/feature_nft_impl/data/source/providers/uniques/UniquesNftProvider.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_nft_impl.data.source.providers.uniques import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber import io.novafoundation.nova.common.data.network.runtime.binding.cast +import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct import io.novafoundation.nova.common.data.network.runtime.binding.getTyped import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.common.utils.uniques @@ -21,7 +22,9 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource -import io.novafoundation.nova.runtime.storage.source.query.singleValueOf +import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder +import io.novafoundation.nova.runtime.storage.source.multi.singleValueOf +import io.novafoundation.nova.runtime.storage.source.query.multi import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.fearless_utils.runtime.metadata.storage @@ -49,29 +52,31 @@ class UniquesNftProvider( val classesIds = classesWithInstances.map { (collection, _) -> collection }.distinct() - val classMetadataStorage = runtime.metadata.uniques().storage("ClassMetadataOf") - val instanceMetadataStorage = runtime.metadata.uniques().storage("InstanceMetadataOf") - val classStorage = runtime.metadata.uniques().storage("Class") + val classMetadataDescriptor: MultiQueryBuilder.Descriptor + val totalIssuanceDescriptor: MultiQueryBuilder.Descriptor + val instanceMetadataDescriptor: MultiQueryBuilder.Descriptor, ByteArray?> val multiQueryResults = multi { - classStorage.querySingleArgKeys(classesIds) - classMetadataStorage.querySingleArgKeys(classesIds) - instanceMetadataStorage.queryKeys(classesWithInstances) + classMetadataDescriptor = runtime.metadata.uniques().storage("ClassMetadataOf").querySingleArgKeys( + keysArgs = classesIds, + keyExtractor = { (classId: BigInteger) -> classId }, + binding = ::bindMetadata + ) + instanceMetadataDescriptor = runtime.metadata.uniques().storage("InstanceMetadataOf").querySingleArgKeys( + keysArgs = classesIds, + keyExtractor = { (classId: BigInteger, instance: BigInteger) -> classId to instance }, + binding = ::bindMetadata + ) + totalIssuanceDescriptor = runtime.metadata.uniques().storage("Class").queryKeys( + keysArgs = classesWithInstances, + keyExtractor = { (classId: BigInteger) -> classId }, + binding = { bindNumber(it.castToStruct()["items"]) } + ) } - val classMetadatas = multiQueryResults.getValue(classMetadataStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() } - .mapValues { (_, parsedValue) -> bindMetadata(parsedValue) } - - val totalIssuances = multiQueryResults.getValue(classStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() } - .mapValues { (_, parsedValue) -> - bindNumber(parsedValue.cast()["items"]) - } - - val instancesMetadatas = multiQueryResults.getValue(instanceMetadataStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() to keyComponents.component2() } - .mapValues { (_, parsedValue) -> bindMetadata(parsedValue) } + val classMetadatas = multiQueryResults[classMetadataDescriptor] + val totalIssuances = multiQueryResults[totalIssuanceDescriptor] + val instancesMetadatas = multiQueryResults[instanceMetadataDescriptor] classesWithInstances.map { (collectionId, instanceId) -> val instanceKey = collectionId to instanceId @@ -136,14 +141,15 @@ class UniquesNftProvider( val classId = nftLocal.collectionId.toBigInteger() remoteStorage.query(chain.id) { - val classMetadataStorage = runtime.metadata.uniques().storage("ClassMetadataOf") - val classStorage = runtime.metadata.uniques().storage("Class") + var classMetadataDescriptor: MultiQueryBuilder.Descriptor<*, ByteArray?> + var classDescriptor: MultiQueryBuilder.Descriptor<*, AccountId> val queryResults = multi { - classMetadataStorage.queryKey(classId) - classStorage.queryKey(classId) + classMetadataDescriptor = runtime.metadata.uniques().storage("ClassMetadataOf").queryKey(classId, binding = ::bindMetadata) + classDescriptor = runtime.metadata.uniques().storage("Class").queryKey(classId, binding = ::bindIssuer) } - val classMetadataPointer = bindMetadata(queryResults.singleValueOf(classMetadataStorage)) + + val classMetadataPointer = queryResults.singleValueOf(classMetadataDescriptor) val collection = if (classMetadataPointer == null) { NftDetails.Collection(nftLocal.collectionId) @@ -158,8 +164,7 @@ class UniquesNftProvider( ) } - val classIssuerRaw = queryResults.singleValueOf(classStorage) - val classIssuer = bindAccountId(classIssuerRaw.cast()["issuer"]) + val classIssuer = queryResults.singleValueOf(classDescriptor) NftDetails( identifier = nftLocal.identifier, @@ -177,6 +182,8 @@ class UniquesNftProvider( } } + private fun bindIssuer(dynamic: Any?): AccountId = bindAccountId(dynamic.castToStruct()["issuer"]) + private fun bindMetadata(dynamic: Any?): ByteArray? = dynamic?.cast()?.getTyped("data") private fun identifier(chainId: ChainId, collectionId: BigInteger, instanceId: BigInteger): String { diff --git a/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/Exposure.kt b/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/Exposure.kt index 70d48bcfb5..adcfe00cd2 100644 --- a/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/Exposure.kt +++ b/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/Exposure.kt @@ -5,3 +5,12 @@ import java.math.BigInteger class IndividualExposure(val who: ByteArray, val value: BigInteger) class Exposure(val total: BigInteger, val own: BigInteger, val others: List) + +class ExposureOverview(val total: BigInteger, val own: BigInteger, val pageCount: BigInteger, val nominatorCount: BigInteger) + +class ExposurePage(val others: List) + +class PagedExposure( + val overview: ExposureOverview, + val pages: List +) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/model/Payout.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/model/Payout.kt index ed1cba6945..91bc945e51 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/model/Payout.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/model/Payout.kt @@ -1,9 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.data.model +import io.novafoundation.nova.common.address.AccountIdKey import java.math.BigInteger class Payout( - val validatorAddress: String, + val validatorStash: AccountIdKey, val era: BigInteger, - val amount: BigInteger + val amount: BigInteger, + val pagesToClaim: List ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/ClaimedRewardPages.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/ClaimedRewardPages.kt new file mode 100644 index 0000000000..292d8c71af --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/ClaimedRewardPages.kt @@ -0,0 +1,15 @@ +package io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings + +import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber +import io.novafoundation.nova.common.data.network.runtime.binding.castToList +import io.novafoundation.nova.common.utils.mapToSet + +typealias ClaimedRewardsPages = Set + +fun bindClaimedPages(decoded: Any?): Set { + val asList = decoded.castToList() + + return asList.mapToSet { item -> + bindNumber(item).toInt() + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/EraRewardPoints.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/EraRewardPoints.kt index 6f98799bc1..ba43d9b6bd 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/EraRewardPoints.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/EraRewardPoints.kt @@ -4,33 +4,27 @@ import io.novafoundation.nova.common.data.network.runtime.binding.HelperBinding import io.novafoundation.nova.common.data.network.runtime.binding.UseCaseBinding import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber -import io.novafoundation.nova.common.data.network.runtime.binding.cast +import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct import io.novafoundation.nova.common.data.network.runtime.binding.getList import io.novafoundation.nova.common.data.network.runtime.binding.requireType import io.novafoundation.nova.common.utils.second import jp.co.soramitsu.fearless_utils.runtime.AccountId -import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.Type -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.Struct -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.fromHexOrNull import java.math.BigInteger -typealias RewardPoint = BigInteger +typealias RewardPoints = BigInteger class EraRewardPoints( - val totalPoints: RewardPoint, + val totalPoints: RewardPoints, val individual: List ) { - class Individual(val accountId: AccountId, val rewardPoints: RewardPoint) + class Individual(val accountId: AccountId, val rewardPoints: RewardPoints) } @UseCaseBinding fun bindEraRewardPoints( - scale: String?, - runtime: RuntimeSnapshot, - type: Type<*>, + decoded: Any? ): EraRewardPoints { - val dynamicInstance = scale?.let { type.fromHexOrNull(runtime, it) }.cast() + val dynamicInstance = decoded.castToStruct() return EraRewardPoints( totalPoints = bindRewardPoint(dynamicInstance["total"]), @@ -46,4 +40,4 @@ fun bindEraRewardPoints( } @HelperBinding -fun bindRewardPoint(dynamicInstance: Any?): RewardPoint = bindNumber(dynamicInstance) +fun bindRewardPoint(dynamicInstance: Any?): RewardPoints = bindNumber(dynamicInstance) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/Exposure.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/Exposure.kt index f4ecaaf67f..eee60b9bf8 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/Exposure.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/Exposure.kt @@ -2,17 +2,16 @@ package io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindi import io.novafoundation.nova.common.data.network.runtime.binding.HelperBinding import io.novafoundation.nova.common.data.network.runtime.binding.UseCaseBinding +import io.novafoundation.nova.common.data.network.runtime.binding.bindList +import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct import io.novafoundation.nova.common.data.network.runtime.binding.incompatible import io.novafoundation.nova.common.data.network.runtime.binding.requireType -import io.novafoundation.nova.common.data.network.runtime.binding.returnType -import io.novafoundation.nova.common.utils.staking import io.novafoundation.nova.feature_staking_api.domain.model.Exposure +import io.novafoundation.nova.feature_staking_api.domain.model.ExposureOverview +import io.novafoundation.nova.feature_staking_api.domain.model.ExposurePage import io.novafoundation.nova.feature_staking_api.domain.model.IndividualExposure -import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.Struct -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.fromHexOrNull -import jp.co.soramitsu.fearless_utils.runtime.metadata.storage import java.math.BigInteger /* @@ -31,22 +30,6 @@ private fun bindIndividualExposure(dynamicInstance: Any?): IndividualExposure { return IndividualExposure(who, value) } -/* - Exposure: { - total: Compact; // total stake of the validator - own: Compact; // own stake of the validator - others: Vec; // nominators stakes -} - */ -@UseCaseBinding -fun bindExposure(scale: String, runtime: RuntimeSnapshot): Exposure { - val type = runtime.metadata.staking().storage("ErasStakers").returnType() - - val decoded = type.fromHexOrNull(runtime, scale) - - return bindExposure(decoded) -} - @UseCaseBinding fun bindExposure(instance: Any?): Exposure { val decoded = instance.castToStruct() @@ -58,3 +41,24 @@ fun bindExposure(instance: Any?): Exposure { return Exposure(total, own, others) } + +@UseCaseBinding +fun bindExposureOverview(instance: Any?): ExposureOverview { + val decoded = instance.castToStruct() + + return ExposureOverview( + total = bindNumber(decoded["total"]), + own = bindNumber(decoded["own"]), + nominatorCount = bindNumber(decoded["nominatorCount"]), + pageCount = bindNumber(decoded["pageCount"]) + ) +} + +@UseCaseBinding +fun bindExposurePage(instance: Any?): ExposurePage { + val decoded = instance.castToStruct() + + return ExposurePage( + others = bindList(decoded["others"], ::bindIndividualExposure) + ) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/StakingLedger.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/StakingLedger.kt index 4242218a3f..f9940add7f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/StakingLedger.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/StakingLedger.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindi import io.novafoundation.nova.common.data.network.runtime.binding.HelperBinding import io.novafoundation.nova.common.data.network.runtime.binding.UseCaseBinding +import io.novafoundation.nova.common.data.network.runtime.binding.bindList import io.novafoundation.nova.common.data.network.runtime.binding.getList import io.novafoundation.nova.common.data.network.runtime.binding.getTyped import io.novafoundation.nova.common.data.network.runtime.binding.incompatible @@ -24,15 +25,18 @@ fun bindStakingLedger(scale: String, runtime: RuntimeSnapshot): StakingLedger { } @UseCaseBinding -fun bindStakingLedger(dynamicInstance: Any): StakingLedger { - requireType(dynamicInstance) +fun bindStakingLedger(decoded: Any): StakingLedger { + requireType(decoded) return StakingLedger( - stashId = dynamicInstance.getTyped("stash"), - total = dynamicInstance.getTyped("total"), - active = dynamicInstance.getTyped("active"), - unlocking = dynamicInstance.getList("unlocking").map(::bindUnlockChunk), - claimedRewards = dynamicInstance.getList("claimedRewards").map(::bindEraIndex) + stashId = decoded.getTyped("stash"), + total = decoded.getTyped("total"), + active = decoded.getTyped("active"), + unlocking = decoded.getList("unlocking").map(::bindUnlockChunk), + claimedRewards = bindList( + dynamicInstance = decoded["claimedRewards"] ?: decoded["legacyClaimedRewards"] ?: emptyList(), + itemBinder = ::bindEraIndex + ) ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/ValidatorPrefs.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/ValidatorPrefs.kt index 84760f42f6..e2179cad31 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/ValidatorPrefs.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/bindings/ValidatorPrefs.kt @@ -1,15 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings -import io.novafoundation.nova.common.data.network.runtime.binding.UseCaseBinding import io.novafoundation.nova.common.data.network.runtime.binding.bindPerbillNumber import io.novafoundation.nova.common.data.network.runtime.binding.castToStruct import io.novafoundation.nova.common.data.network.runtime.binding.getTyped -import io.novafoundation.nova.common.data.network.runtime.binding.returnType -import io.novafoundation.nova.common.utils.staking import io.novafoundation.nova.feature_staking_api.domain.model.ValidatorPrefs -import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.fromHexOrNull -import jp.co.soramitsu.fearless_utils.runtime.metadata.storage private const val BLOCKED_DEFAULT = false @@ -21,11 +15,3 @@ fun bindValidatorPrefs(decoded: Any?): ValidatorPrefs { blocked = asStruct["blocked"] ?: BLOCKED_DEFAULT ) } - -@UseCaseBinding -fun bindValidatorPrefs(scale: String, runtime: RuntimeSnapshot): ValidatorPrefs { - val type = runtime.metadata.staking().storage("Validators").returnType() - val decoded = type.fromHexOrNull(runtime, scale) - - return bindValidatorPrefs(decoded) -} 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 2f903dd784..2684b1d0f5 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 @@ -50,17 +50,6 @@ fun ExtrinsicBuilder.nominate(targets: List): ExtrinsicBuilder { ) } -fun ExtrinsicBuilder.payoutStakers(era: BigInteger, validatorId: AccountId): ExtrinsicBuilder { - return call( - "Staking", - "payout_stakers", - mapOf( - "validator_stash" to validatorId, - "era" to era - ) - ) -} - fun ExtrinsicBuilder.bondMore(amount: BigInteger): ExtrinsicBuilder { return call( "Staking", diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/Common.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/Common.kt index 7d488089b1..6ec56a6789 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/Common.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/Common.kt @@ -4,23 +4,13 @@ import io.novafoundation.nova.common.data.network.rpc.BulkRetriever import io.novafoundation.nova.common.utils.staking import io.novafoundation.nova.core.model.StorageEntry import io.novafoundation.nova.core.storage.StorageCache -import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindActiveEra -import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.metadata.RuntimeMetadata import jp.co.soramitsu.fearless_utils.runtime.metadata.storage import jp.co.soramitsu.fearless_utils.runtime.metadata.storageKey import jp.co.soramitsu.fearless_utils.wsrpc.SocketService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import java.math.BigInteger fun RuntimeMetadata.activeEraStorageKey() = staking().storage("ActiveEra").storageKey() -suspend fun StorageCache.observeActiveEraIndex(runtime: RuntimeSnapshot, chainId: String): Flow { - return observeEntry(runtime.metadata.activeEraStorageKey(), chainId) - .map { bindActiveEra(it.content!!, runtime) } -} - suspend fun BulkRetriever.fetchValuesToCache( socketService: SocketService, keys: List, @@ -34,13 +24,20 @@ suspend fun BulkRetriever.fetchValuesToCache( storageCache.insert(toInsert, chainId) } +/** + * @return number of fetched keys + */ suspend fun BulkRetriever.fetchPrefixValuesToCache( socketService: SocketService, prefix: String, storageCache: StorageCache, chainId: String -) { +): Int { val allKeys = retrieveAllKeys(socketService, prefix) - fetchValuesToCache(socketService, allKeys, storageCache, chainId) + if (allKeys.isNotEmpty()) { + fetchValuesToCache(socketService, allKeys, storageCache, chainId) + } + + return allKeys.size } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/ValidatorExposureUpdater.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/ValidatorExposureUpdater.kt index b5d0fcbe8d..a93cc16001 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/ValidatorExposureUpdater.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/ValidatorExposureUpdater.kt @@ -1,72 +1,197 @@ package io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters import io.novafoundation.nova.common.data.network.rpc.BulkRetriever +import io.novafoundation.nova.common.utils.hasStorage import io.novafoundation.nova.common.utils.staking +import io.novafoundation.nova.core.model.StorageEntry import io.novafoundation.nova.core.storage.StorageCache -import io.novafoundation.nova.core.updater.GlobalScopeUpdater import io.novafoundation.nova.core.updater.SharedRequestsBuilder import io.novafoundation.nova.core.updater.Updater +import io.novafoundation.nova.feature_staking_api.domain.model.EraIndex import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.ValidatorExposureUpdater.Companion.STORAGE_KEY_PAGED_EXPOSURES import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.base.StakingUpdater +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.scope.ActiveEraScope import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.getRuntime import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.metadata.storage import jp.co.soramitsu.fearless_utils.runtime.metadata.storageKey import jp.co.soramitsu.fearless_utils.wsrpc.SocketService -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filterNot -import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.flow import java.math.BigInteger +/** + * Manages sync for validators exposures + * Depending on the version of the staking pallet, exposures might be stored differently: + * + * 1. Legacy version - exposures are stored in `EraStakers` storage + * 2. Current version - exposures are paged and stored in two storages: `EraStakersPaged` and `EraStakersOverview` + * + * Note that during the transition from Legacy to Current version during next [Staking.historyDepth] + * eras older storage will still be present and filled with previous era information. Old storage will be cleared on era-by-era basis once new era happens. + * Also, right after chain has just upgraded, `EraStakersPaged` will be empty untill next era happens. + * + * The updater takes care of that all also storing special [STORAGE_KEY_PAGED_EXPOSURES] indicating whether + * paged exposures are actually present for the latest era or not + */ class ValidatorExposureUpdater( private val bulkRetriever: BulkRetriever, private val stakingSharedState: StakingSharedState, private val chainRegistry: ChainRegistry, - private val storageCache: StorageCache -) : GlobalScopeUpdater, StakingUpdater { + private val storageCache: StorageCache, + override val scope: ActiveEraScope, +) : StakingUpdater { + + companion object { + + const val STORAGE_KEY_PAGED_EXPOSURES = "NovaWallet.Staking.PagedExposuresUsed" + + fun isPagedExposuresValue(enabled: Boolean) = enabled.toString() + + fun decodeIsPagedExposuresValue(value: String?) = value.toBoolean() + } override suspend fun listenForUpdates( storageSubscriptionBuilder: SharedRequestsBuilder, - scopeValue: Unit, + scopeValue: EraIndex, ): Flow { + @Suppress("UnnecessaryVariable") + val activeEra = scopeValue val socketService = storageSubscriptionBuilder.socketService ?: return emptyFlow() - val chainId = stakingSharedState.chainId() - val runtime = chainRegistry.getRuntime(chainId) + return flow { + val chainId = stakingSharedState.chainId() + val runtime = chainRegistry.getRuntime(chainId) + + if (checkValuesInCache(activeEra, chainId, runtime)) { + return@flow + } + + cleanupOutdatedEras(chainId, runtime) - return storageCache.observeActiveEraIndex(runtime, chainId) - .map { activeEraIndex -> runtime.eraStakersPrefixFor(activeEraIndex) } - .filterNot { storageCache.isPrefixInCache(it, chainId) } - .onEach { cleanupPreviousEras(runtime, chainId) } - .onEach { updateNominatorsForEra(it, socketService, chainId) } - .flowOn(Dispatchers.IO) - .noSideAffects() + syncNewExposures(activeEra, runtime, socketService, chainId) + }.noSideAffects() } - private suspend fun cleanupPreviousEras(runtimeSnapshot: RuntimeSnapshot, chainId: String) { - val prefix = runtimeSnapshot.eraStakersPrefix() + private suspend fun checkValuesInCache(era: BigInteger, chainId: String, runtimeSnapshot: RuntimeSnapshot): Boolean { + if (runtimeSnapshot.pagedExposuresEnabled()) { + return isPagedExposuresInCache(era, chainId, runtimeSnapshot) || isLegacyExposuresInCache(era, chainId, runtimeSnapshot) + } - storageCache.removeByPrefix(prefix, chainId) + return isLegacyExposuresInCache(era, chainId, runtimeSnapshot) } - private fun RuntimeSnapshot.eraStakersPrefixFor(era: BigInteger): String { - return metadata.staking().storage("ErasStakers").storageKey(this, era) + private suspend fun isPagedExposuresInCache(era: BigInteger, chainId: String, runtimeSnapshot: RuntimeSnapshot): Boolean { + val prefix = runtimeSnapshot.eraStakersOverviewPrefixFor(era) + + return storageCache.isPrefixInCache(prefix, chainId) } - private fun RuntimeSnapshot.eraStakersPrefix(): String { - return metadata.staking().storage("ErasStakers").storageKey(this) + private suspend fun isLegacyExposuresInCache(era: BigInteger, chainId: String, runtimeSnapshot: RuntimeSnapshot): Boolean { + val prefix = runtimeSnapshot.eraStakersPrefixFor(era) + + return storageCache.isPrefixInCache(prefix, chainId) } - private suspend fun updateNominatorsForEra( - eraStakersPrefix: String, + private suspend fun cleanupOutdatedEras(chainId: String, runtimeSnapshot: RuntimeSnapshot) { + if (runtimeSnapshot.pagedExposuresEnabled()) { + cleanupPagedExposures(runtimeSnapshot, chainId) + } + + cleanupLegacyExposures(runtimeSnapshot, chainId) + } + + private suspend fun cleanupLegacyExposures(runtimeSnapshot: RuntimeSnapshot, chainId: String) { + storageCache.removeByPrefix(runtimeSnapshot.eraStakersPrefix(), chainId) + } + + private suspend fun cleanupPagedExposures(runtimeSnapshot: RuntimeSnapshot, chainId: String) { + storageCache.removeByPrefix(runtimeSnapshot.eraStakersPagedPrefix(), chainId) + storageCache.removeByPrefix(runtimeSnapshot.eraStakersOverviewPrefix(), chainId) + } + + private suspend fun syncNewExposures(era: BigInteger, runtimeSnapshot: RuntimeSnapshot, socketService: SocketService, chainId: String) { + var pagedExposureUsed = false + + if (runtimeSnapshot.pagedExposuresEnabled()) { + val pagedExposuresWerePresent = tryFetchingPagedExposures(era, runtimeSnapshot, socketService, chainId) + + if (!pagedExposuresWerePresent) { + fetchLegacyExposures(era, runtimeSnapshot, socketService, chainId) + } else { + pagedExposureUsed = true + } + } else { + fetchLegacyExposures(era, runtimeSnapshot, socketService, chainId) + } + + saveIsExposuresUsedFlag(pagedExposureUsed, chainId) + } + + private suspend fun tryFetchingPagedExposures( + era: BigInteger, + runtimeSnapshot: RuntimeSnapshot, + socketService: SocketService, + chainId: String + ): Boolean = runCatching { + val overviewPrefix = runtimeSnapshot.eraStakersOverviewPrefixFor(era) + val numberOfKeysSynced = bulkRetriever.fetchPrefixValuesToCache(socketService, overviewPrefix, storageCache, chainId) + + val pagedExposuresPresent = numberOfKeysSynced > 0 + + if (pagedExposuresPresent) { + val pagedExposuresPrefix = runtimeSnapshot.eraStakersPagedPrefixFor(era) + bulkRetriever.fetchPrefixValuesToCache(socketService, pagedExposuresPrefix, storageCache, chainId) + } + + pagedExposuresPresent + }.getOrDefault(false) + + private suspend fun fetchLegacyExposures( + era: BigInteger, + runtimeSnapshot: RuntimeSnapshot, socketService: SocketService, chainId: String - ) = runCatching { - bulkRetriever.fetchPrefixValuesToCache(socketService, eraStakersPrefix, storageCache, chainId) + ): Result = runCatching { + val prefix = runtimeSnapshot.eraStakersPrefixFor(era) + bulkRetriever.fetchPrefixValuesToCache(socketService, prefix, storageCache, chainId) + } + + private suspend fun saveIsExposuresUsedFlag(isUsed: Boolean, chainId: String) { + val encodedValue = isPagedExposuresValue(isUsed) + val entry = StorageEntry(STORAGE_KEY_PAGED_EXPOSURES, encodedValue) + + storageCache.insert(entry, chainId) + } + + private fun RuntimeSnapshot.pagedExposuresEnabled(): Boolean { + return metadata.staking().hasStorage("ErasStakersPaged") + } + + private fun RuntimeSnapshot.eraStakersPrefixFor(era: BigInteger): String { + return metadata.staking().storage("ErasStakers").storageKey(this, era) + } + + private fun RuntimeSnapshot.eraStakersOverviewPrefixFor(era: BigInteger): String { + return metadata.staking().storage("ErasStakersOverview").storageKey(this, era) + } + + private fun RuntimeSnapshot.eraStakersOverviewPrefix(): String { + return metadata.staking().storage("ErasStakersOverview").storageKey(this) + } + + private fun RuntimeSnapshot.eraStakersPagedPrefixFor(era: BigInteger): String { + return metadata.staking().storage("ErasStakersPaged").storageKey(this, era) + } + + private fun RuntimeSnapshot.eraStakersPagedPrefix(): String { + return metadata.staking().storage("ErasStakersPaged").storageKey(this) + } + + private fun RuntimeSnapshot.eraStakersPrefix(): String { + return metadata.staking().storage("ErasStakers").storageKey(this) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalTotalValidatorRewardUpdater.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalTotalValidatorRewardUpdater.kt index 5ed3102f66..5f8b284469 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalTotalValidatorRewardUpdater.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalTotalValidatorRewardUpdater.kt @@ -4,14 +4,9 @@ import io.novafoundation.nova.common.utils.staking import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.metadata.storage import jp.co.soramitsu.fearless_utils.runtime.metadata.storageKey -import java.math.BigInteger class HistoricalTotalValidatorRewardUpdater : HistoricalUpdater { - override fun constructHistoricalKey(runtime: RuntimeSnapshot, era: BigInteger): String { - return runtime.metadata.staking().storage("ErasValidatorReward").storageKey(runtime, era) - } - override fun constructKeyPrefix(runtime: RuntimeSnapshot): String { return runtime.metadata.staking().storage("ErasValidatorReward").storageKey(runtime) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalUpdateMediator.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalUpdateMediator.kt index 7b4d0aba1b..33a92e9a6d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalUpdateMediator.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalUpdateMediator.kt @@ -1,31 +1,22 @@ package io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.historical -import io.novafoundation.nova.common.data.network.rpc.BulkRetriever +import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core.updater.SharedRequestsBuilder import io.novafoundation.nova.core.updater.Updater -import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository -import io.novafoundation.nova.feature_staking_api.domain.api.historicalEras import io.novafoundation.nova.feature_staking_api.domain.model.EraIndex import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.base.StakingUpdater -import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.fetchValuesToCache import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.scope.ActiveEraScope import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.getRuntime import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.onEach -import java.math.BigInteger interface HistoricalUpdater { - fun constructHistoricalKey(runtime: RuntimeSnapshot, era: BigInteger): String - fun constructKeyPrefix(runtime: RuntimeSnapshot): String } @@ -33,41 +24,35 @@ class HistoricalUpdateMediator( override val scope: ActiveEraScope, private val historicalUpdaters: List, private val stakingSharedState: StakingSharedState, - private val bulkRetriever: BulkRetriever, - private val stakingRepository: StakingRepository, private val storageCache: StorageCache, private val chainRegistry: ChainRegistry, + private val preferences: Preferences, ) : Updater, StakingUpdater { override suspend fun listenForUpdates(storageSubscriptionBuilder: SharedRequestsBuilder, scopeValue: EraIndex): Flow { val chainId = stakingSharedState.chainId() val runtime = chainRegistry.getRuntime(chainId) - val socketService = storageSubscriptionBuilder.socketService ?: return emptyFlow() - return flowOf { - val allKeysNeeded = constructHistoricalKeys(chainId, runtime) - val keysInDataBase = storageCache.filterKeysInCache(allKeysNeeded, chainId).toSet() + if (isHistoricalDataCleared(chainId)) return@flowOf null - val missingKeys = allKeysNeeded.filter { it !in keysInDataBase } + val prefixes = historicalUpdaters.map { it.constructKeyPrefix(runtime) } + prefixes.onEach { storageCache.removeByPrefix(prefixKey = it, chainId) } - allKeysNeeded to missingKeys + setHistoricalDataCleared(chainId) } - .filter { (_, missing) -> missing.isNotEmpty() } - .onEach { (allNeeded, missing) -> - val prefixes = historicalUpdaters.map { it.constructKeyPrefix(runtime) } - prefixes.onEach { storageCache.removeByPrefixExcept(prefixKey = it, fullKeyExceptions = allNeeded, chainId) } - - bulkRetriever.fetchValuesToCache(socketService, missing, storageCache, chainId) - } .noSideAffects() } - private suspend fun constructHistoricalKeys(chainId: ChainId, runtime: RuntimeSnapshot): List { - val historicalRange = stakingRepository.historicalEras(chainId) + private fun isHistoricalDataCleared(chainId: ChainId): Boolean { + return preferences.contains(isHistoricalDataClearedKey(chainId)) + } - return historicalUpdaters.flatMap { updater -> - historicalRange.map { updater.constructHistoricalKey(runtime, it) } - } + private fun setHistoricalDataCleared(chainId: ChainId) { + preferences.putBoolean(isHistoricalDataClearedKey(chainId), true) + } + + private fun isHistoricalDataClearedKey(chainId: ChainId): String { + return "HistoricalUpdateMediator.HistoricalDataCleared::$chainId" } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalValidatorRewardPointsUpdater.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalValidatorRewardPointsUpdater.kt index 17c3cb13b0..aadee6820d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalValidatorRewardPointsUpdater.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/historical/HistoricalValidatorRewardPointsUpdater.kt @@ -4,14 +4,9 @@ import io.novafoundation.nova.common.utils.staking import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.metadata.storage import jp.co.soramitsu.fearless_utils.runtime.metadata.storageKey -import java.math.BigInteger class HistoricalValidatorRewardPointsUpdater : HistoricalUpdater { - override fun constructHistoricalKey(runtime: RuntimeSnapshot, era: BigInteger): String { - return runtime.metadata.staking().storage("ErasRewardPoints").storageKey(runtime, era) - } - override fun constructKeyPrefix(runtime: RuntimeSnapshot): String { return runtime.metadata.staking().storage("ErasRewardPoints").storageKey(runtime) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/StakingApi.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/StakingApi.kt index 8bd4014328..7df2dca154 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/StakingApi.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/StakingApi.kt @@ -4,7 +4,8 @@ import io.novafoundation.nova.common.data.network.subquery.EraValidatorInfoQuery import io.novafoundation.nova.common.data.network.subquery.SubQueryResponse import io.novafoundation.nova.feature_staking_impl.data.network.subquery.request.DirectStakingPeriodRewardsRequest import io.novafoundation.nova.feature_staking_impl.data.network.subquery.request.PoolStakingPeriodRewardsRequest -import io.novafoundation.nova.feature_staking_impl.data.network.subquery.request.StakingEraValidatorInfosRequest +import io.novafoundation.nova.feature_staking_impl.data.network.subquery.request.StakingNominatorEraInfosRequest +import io.novafoundation.nova.feature_staking_impl.data.network.subquery.request.StakingValidatorEraInfosRequest import io.novafoundation.nova.feature_staking_impl.data.network.subquery.response.StakingPeriodRewardsResponse import retrofit2.http.Body import retrofit2.http.POST @@ -25,8 +26,14 @@ interface StakingApi { ): SubQueryResponse @POST - suspend fun getValidatorsInfo( + suspend fun getNominatorEraInfos( @Url url: String, - @Body body: StakingEraValidatorInfosRequest + @Body body: StakingNominatorEraInfosRequest + ): SubQueryResponse + + @POST + suspend fun getValidatorEraInfos( + @Url url: String, + @Body body: StakingValidatorEraInfosRequest ): SubQueryResponse } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/SubQueryValidatorSetFetcher.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/SubQueryValidatorSetFetcher.kt index 96784bac4f..184a3a7e61 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/SubQueryValidatorSetFetcher.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/SubQueryValidatorSetFetcher.kt @@ -1,33 +1,69 @@ package io.novafoundation.nova.feature_staking_impl.data.network.subquery -import io.novafoundation.nova.common.data.network.subquery.EraValidatorInfoQueryResponse.EraValidatorInfo.Nodes.Node -import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository -import io.novafoundation.nova.feature_staking_api.domain.api.historicalEras +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.common.address.intoKey +import io.novafoundation.nova.common.data.network.subquery.EraValidatorInfoQueryResponse +import io.novafoundation.nova.common.data.network.subquery.SubQueryResponse +import io.novafoundation.nova.feature_staking_api.domain.model.EraIndex import io.novafoundation.nova.feature_staking_impl.data.model.stakingExternalApi -import io.novafoundation.nova.feature_staking_impl.data.network.subquery.request.StakingEraValidatorInfosRequest +import io.novafoundation.nova.feature_staking_impl.data.network.subquery.request.StakingNominatorEraInfosRequest +import io.novafoundation.nova.feature_staking_impl.data.network.subquery.request.StakingValidatorEraInfosRequest import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAccountId +import java.math.BigInteger + +data class PayoutTarget(val validatorStash: AccountIdKey, val era: BigInteger) class SubQueryValidatorSetFetcher( private val stakingApi: StakingApi, - private val stakingRepository: StakingRepository, ) { - suspend fun fetchAllValidators(chain: Chain, stashAccountAddress: String): List { - val historicalRange = stakingRepository.historicalEras(chain.id) + suspend fun findNominatorPayoutTargets( + chain: Chain, + stashAccountAddress: String, + eraRange: List, + ): List { + return findPayoutTargets(chain) { apiUrl -> + stakingApi.getNominatorEraInfos( + apiUrl, + StakingNominatorEraInfosRequest( + eraFrom = eraRange.first(), + eraTo = eraRange.last(), + nominatorStashAddress = stashAccountAddress + ) + ) + } + } + + suspend fun findValidatorPayoutTargets( + chain: Chain, + stashAccountAddress: String, + eraRange: List, + ): List { + return findPayoutTargets(chain) { apiUrl -> + stakingApi.getValidatorEraInfos( + apiUrl, + StakingValidatorEraInfosRequest( + eraFrom = eraRange.first(), + eraTo = eraRange.last(), + validatorStashAddress = stashAccountAddress + ) + ) + } + } + private suspend fun findPayoutTargets( + chain: Chain, + apiCall: suspend (url: String) -> SubQueryResponse + ): List { val stakingExternalApi = chain.stakingExternalApi() ?: return emptyList() - val validatorsInfos = stakingApi.getValidatorsInfo( - stakingExternalApi.url, - StakingEraValidatorInfosRequest( - eraFrom = historicalRange.first(), - eraTo = historicalRange.last(), - accountAddress = stashAccountAddress - ) - ) + val validatorsInfos = apiCall(stakingExternalApi.url) + + val nodes = validatorsInfos.data.eraValidatorInfos?.nodes.orEmpty() - return validatorsInfos.data.query?.eraValidatorInfos?.nodes?.map( - Node::address - )?.distinct().orEmpty() + return nodes.map { + PayoutTarget(it.address.toAccountId().intoKey(), it.era) + } } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingEraValidatorInfosRequest.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingNominatorEraInfosRequest.kt similarity index 75% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingEraValidatorInfosRequest.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingNominatorEraInfosRequest.kt index 46763e4976..d3b11f6e9e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingEraValidatorInfosRequest.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingNominatorEraInfosRequest.kt @@ -2,14 +2,13 @@ package io.novafoundation.nova.feature_staking_impl.data.network.subquery.reques import java.math.BigInteger -class StakingEraValidatorInfosRequest(eraFrom: BigInteger, eraTo: BigInteger, accountAddress: String) { +class StakingNominatorEraInfosRequest(eraFrom: BigInteger, eraTo: BigInteger, nominatorStashAddress: String) { val query = """ - { query { eraValidatorInfos( filter:{ era:{ greaterThanOrEqualTo: $eraFrom, lessThanOrEqualTo: $eraTo}, - others:{ contains:[{who: "$accountAddress"}]} + others:{ contains:[{who: "$nominatorStashAddress"}]} } ) { nodes { @@ -21,6 +20,5 @@ class StakingEraValidatorInfosRequest(eraFrom: BigInteger, eraTo: BigInteger, ac } } } - } """.trimIndent() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingValidatorEraInfosRequest.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingValidatorEraInfosRequest.kt new file mode 100644 index 0000000000..eb63c57372 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/subquery/request/StakingValidatorEraInfosRequest.kt @@ -0,0 +1,25 @@ +package io.novafoundation.nova.feature_staking_impl.data.network.subquery.request + +import io.novafoundation.nova.common.data.network.subquery.SubQueryFilters +import java.math.BigInteger + +class StakingValidatorEraInfosRequest(eraFrom: BigInteger, eraTo: BigInteger, validatorStashAddress: String) : SubQueryFilters { + val query = """ + query { + eraValidatorInfos( + filter:{ + era:{ greaterThanOrEqualTo: $eraFrom, lessThanOrEqualTo: $eraTo}, + ${"address" equalTo validatorStashAddress} + } + ) { + nodes { + id + address + era + total + own + } + } + } + """.trimIndent() +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/PayoutRepository.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/PayoutRepository.kt index c90e76a3e6..91db20510c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/PayoutRepository.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/PayoutRepository.kt @@ -1,86 +1,84 @@ package io.novafoundation.nova.feature_staking_impl.data.repository -import io.novafoundation.nova.common.data.network.rpc.BulkRetriever -import io.novafoundation.nova.common.data.network.runtime.binding.BinderWithType -import io.novafoundation.nova.common.data.network.runtime.binding.returnType -import io.novafoundation.nova.common.utils.mapValuesNotNull +import android.util.Log +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.common.address.intoKey +import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId +import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber +import io.novafoundation.nova.common.utils.findPartitionPoint +import io.novafoundation.nova.common.utils.hasStorage +import io.novafoundation.nova.common.utils.mapToSet +import io.novafoundation.nova.common.utils.reversed import io.novafoundation.nova.common.utils.staking -import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.domain.api.historicalEras +import io.novafoundation.nova.feature_staking_api.domain.model.EraIndex import io.novafoundation.nova.feature_staking_api.domain.model.Exposure -import io.novafoundation.nova.feature_staking_api.domain.model.StakingLedger +import io.novafoundation.nova.feature_staking_api.domain.model.ExposureOverview +import io.novafoundation.nova.feature_staking_api.domain.model.ExposurePage +import io.novafoundation.nova.feature_staking_api.domain.model.PagedExposure import io.novafoundation.nova.feature_staking_api.domain.model.ValidatorPrefs import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState import io.novafoundation.nova.feature_staking_impl.data.model.Payout +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.ClaimedRewardsPages import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.EraRewardPoints +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindClaimedPages import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindEraRewardPoints import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindExposure +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindExposureOverview +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindExposurePage import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindStakingLedger -import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindTotalValidatorEraReward import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindValidatorPrefs +import io.novafoundation.nova.feature_staking_impl.data.network.subquery.PayoutTarget import io.novafoundation.nova.feature_staking_impl.data.network.subquery.SubQueryValidatorSetFetcher -import io.novafoundation.nova.runtime.ext.accountIdOf +import io.novafoundation.nova.feature_staking_impl.data.repository.PayoutRepository.ValidatorEraStake.NominatorInfo +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.getRuntime -import io.novafoundation.nova.runtime.multiNetwork.getSocket -import jp.co.soramitsu.fearless_utils.extensions.fromHex -import jp.co.soramitsu.fearless_utils.extensions.toHexString +import io.novafoundation.nova.runtime.network.rpc.RpcCalls +import io.novafoundation.nova.runtime.storage.source.StorageDataSource +import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder +import io.novafoundation.nova.runtime.storage.source.query.DynamicInstanceBinder +import io.novafoundation.nova.runtime.storage.source.query.metadata +import io.novafoundation.nova.runtime.storage.source.query.multi +import io.novafoundation.nova.runtime.storage.source.query.wrapSingleArgumentKeys +import jp.co.soramitsu.fearless_utils.hash.isPositive import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.metadata.module.StorageEntry import jp.co.soramitsu.fearless_utils.runtime.metadata.storage import jp.co.soramitsu.fearless_utils.runtime.metadata.storageKey -import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAccountId -import jp.co.soramitsu.fearless_utils.wsrpc.SocketService import java.math.BigInteger typealias HistoricalMapping = Map // EraIndex -> T -class ValidatorHistoricalStats( - val validatorAddress: String, - val ledger: StakingLedger, - val infoHistory: HistoricalMapping, -) { - - class ValidatorEraStats( - val prefs: ValidatorPrefs, - val exposure: Exposure, - ) -} - -private class RewardCalculationContext( - val validatorEraStats: ValidatorHistoricalStats.ValidatorEraStats, - val eraValidatorPointsDistribution: EraRewardPoints, - val validatorAccountId: AccountId, - val totalEraReward: BigInteger -) - class PayoutRepository( private val stakingRepository: StakingRepository, - private val bulkRetriever: BulkRetriever, private val validatorSetFetcher: SubQueryValidatorSetFetcher, - private val storageCache: StorageCache, private val chainRegistry: ChainRegistry, + private val remoteStorage: StorageDataSource, + private val rpcCalls: RpcCalls, ) { suspend fun calculateUnpaidPayouts(stakingState: StakingState.Stash): List { return when (stakingState) { is StakingState.Stash.Nominator -> calculateUnpaidPayouts( chain = stakingState.chain, - retrieveValidatorAddresses = { - validatorSetFetcher.fetchAllValidators(stakingState.chain, stakingState.stashAddress) + retrievePayoutTargets = { historicalRange -> + validatorSetFetcher.findNominatorPayoutTargets(stakingState.chain, stakingState.stashAddress, historicalRange) }, - calculatePayoutReward = { + calculatePayout = { calculateNominatorReward(stakingState.stashId, it) } ) is StakingState.Stash.Validator -> calculateUnpaidPayouts( chain = stakingState.chain, - retrieveValidatorAddresses = { listOf(stakingState.stashAddress) }, - calculatePayoutReward = ::calculateValidatorReward + retrievePayoutTargets = { historicalRange -> + validatorSetFetcher.findValidatorPayoutTargets(stakingState.chain, stakingState.stashAddress, historicalRange) + }, + calculatePayout = ::calculateValidatorReward ) else -> throw IllegalStateException("Cannot calculate payouts for ${stakingState::class.simpleName} state") } @@ -88,77 +86,208 @@ class PayoutRepository( private suspend fun calculateUnpaidPayouts( chain: Chain, - retrieveValidatorAddresses: suspend () -> List, - calculatePayoutReward: (RewardCalculationContext) -> BigInteger? + retrievePayoutTargets: suspend (historicalRange: List) -> List, + calculatePayout: (RewardCalculationContext) -> Payout? ): List { val chainId = chain.id val runtime = chainRegistry.getRuntime(chain.id) - val socketService = chainRegistry.getSocket(chain.id) - - val validatorAddresses = retrieveValidatorAddresses() val historicalRange = stakingRepository.historicalEras(chainId) - val validatorStats = getValidatorHistoricalStats(runtime, socketService, historicalRange, validatorAddresses) - val historicalRangeSet = historicalRange.toSet() - - val historicalTotalEraRewards = retrieveTotalEraReward(chainId, runtime, historicalRange) - val historicalRewardDistribution = retrieveEraPointsDistribution(chainId, runtime, historicalRange) - - return validatorStats.map { stats -> - val claimedRewardsHoles = historicalRangeSet - stats.ledger.claimedRewards.toSet() - val validatorAddress = stats.validatorAddress - - claimedRewardsHoles.mapNotNull { holeEra -> - val rewardCalculationContext = RewardCalculationContext( - validatorEraStats = stats.infoHistory[holeEra] ?: return@mapNotNull null, - validatorAccountId = chain.accountIdOf(validatorAddress), - totalEraReward = historicalTotalEraRewards[holeEra]!!, - eraValidatorPointsDistribution = historicalRewardDistribution[holeEra]!! - ) + val payoutTargets = retrievePayoutTargets(historicalRange) + + Log.d("PayoutRepository", "Fetched payoutTargets") + + val involvedEras = payoutTargets.map { it.era }.distinct() + + val validatorEraStakes = getValidatorHistoricalStats(chainId, runtime, historicalRange, payoutTargets) + + val historicalTotalEraRewards = retrieveTotalEraReward(chainId, runtime, involvedEras) + Log.d("PayoutRepository", "Fetched historicalTotalEraRewards") + + val historicalRewardDistribution = retrieveEraPointsDistribution(chainId, runtime, involvedEras) + Log.d("PayoutRepository", "Fetched historicalRewardDistribution") + + val eraClaims = fetchValidatorEraClaims(chain, payoutTargets) + Log.d("PayoutRepository", "Fetched eraClaims") + + return validatorEraStakes.mapNotNull { validatorEraStake -> + val key = validatorEraStake.era to validatorEraStake.stash + val eraPointsDistribution = historicalRewardDistribution[validatorEraStake.era] ?: return@mapNotNull null + + val rewardCalculationContext = RewardCalculationContext( + eraStake = validatorEraStake, + eraClaim = eraClaims[key] ?: return@mapNotNull null, + totalEraReward = historicalTotalEraRewards[validatorEraStake.era] ?: return@mapNotNull null, + eraPoints = eraPointsDistribution + ) + + calculatePayout(rewardCalculationContext) + }.also { + Log.d("PayoutRepository", "Constructed payouts") + } + } + + private suspend fun partitionHistoricalRangeByPagedExposurePresence( + historicalRange: List, + runtime: RuntimeSnapshot, + chainId: ChainId + ): PartitionedHistoricalRange { + val firstPagedExposureEra = findFirstPagedExposureIndex(historicalRange, runtime, chainId) + ?: return PartitionedHistoricalRange(legacy = historicalRange, paged = emptyList()) + + return PartitionedHistoricalRange( + legacy = historicalRange.subList(0, firstPagedExposureEra), + paged = historicalRange.subList(firstPagedExposureEra, historicalRange.size) + ) + } + + private suspend fun findFirstPagedExposureIndex( + historicalRange: List, + runtime: RuntimeSnapshot, + chainId: ChainId + ): Int? { + val oldestEra = historicalRange.first() + + if (!runtime.metadata.staking().hasStorage("ErasStakersOverview")) return null + if (!runtime.metadata.staking().hasStorage("ErasStakersClipped")) return 0 + + // We expect certain delay between all legacy exposures are gone and runtime upgrade that removes storages completely + // This small optimizations reduces the number of requests needed to 1 for such period despite adding 1 extra request during migration period + if (isExposurePaged(oldestEra, runtime, chainId)) return 0 + + return historicalRange.findPartitionPoint { era -> isExposurePaged(era, runtime, chainId) }.also { + Log.d("PayoutRepository", "Fount first paged exposures era: ${it?.let { historicalRange[it] }}") + } + } - val reward = calculatePayoutReward(rewardCalculationContext) + private suspend fun isExposurePaged(eraIndex: EraIndex, runtime: RuntimeSnapshot, chainId: ChainId): Boolean { + val pagedEraPrefix = runtime.metadata.staking().storage("ErasStakersOverview").storageKey(runtime, eraIndex) + val storageSize = rpcCalls.getStorageSize(chainId, pagedEraPrefix) - reward?.let { Payout(validatorAddress, holeEra, it) } + Log.d("PayoutRepository", "Fetched storage size for era $eraIndex: $storageSize") + + return storageSize.isPositive() + } + + private suspend fun fetchValidatorEraClaims( + chain: Chain, + payoutTargets: List, + ): Map, ValidatorEraClaim> { + return remoteStorage.query(chain.id) { + var validatorsPrefsDescriptor: MultiQueryBuilder.Descriptor, ValidatorPrefs?> + var controllersDescriptor: MultiQueryBuilder.Descriptor + var claimedPagesDescriptor: MultiQueryBuilder.Descriptor, ClaimedRewardsPages?>? = null + + val multiQueryResults = multi { + val eraAndValidatorKeys = payoutTargets.map { listOf(it.era, it.validatorStash.value) } + val validatorKeys = payoutTargets.mapToSet { it.validatorStash }.map { listOf(it.value) } + + validatorsPrefsDescriptor = runtime.metadata.staking().storage("ErasValidatorPrefs").queryKeys( + keysArgs = eraAndValidatorKeys, + keyExtractor = { (era: EraIndex, validatorStash: AccountId) -> era to validatorStash.intoKey() }, + binding = { decoded -> decoded?.let { bindValidatorPrefs(decoded) } } + ) + controllersDescriptor = runtime.metadata.staking().storage("Bonded").queryKeys( + keysArgs = validatorKeys, + keyExtractor = { (validatorStash: AccountId) -> validatorStash.intoKey() }, + binding = { decoded -> decoded?.let { bindAccountId(decoded).intoKey() } } + ) + if (metadata.staking().hasStorage("ClaimedRewards")) { + claimedPagesDescriptor = runtime.metadata.staking().storage("ClaimedRewards").queryKeys( + keysArgs = eraAndValidatorKeys, + keyExtractor = { (era: EraIndex, validatorStash: AccountId) -> era to validatorStash.intoKey() }, + binding = { decoded -> decoded?.let { bindClaimedPages(decoded) } } + ) + } } - }.flatten() + + Log.d("PayoutRepository", "Fetched prefs, controllers and claimed pages") + + val validatorsPrefs = multiQueryResults[validatorsPrefsDescriptor] + val controllers = multiQueryResults[controllersDescriptor] + + val stashByController = controllers + .mapValues { (stash, controller) -> controller ?: stash } + .reversed() + + val claimedLegacyRewards = metadata.staking().storage("Ledger").entries( + keysArguments = stashByController.keys.map { listOf(it.value) }, + keyExtractor = { (controller: AccountId) -> stashByController.getValue(controller.intoKey()) }, + binding = { decoded, _ -> decoded?.let { bindStakingLedger(decoded).claimedRewards.toSet() } } + ) + + Log.d("PayoutRepository", "Fetched claimedLegacyRewards") + + val allClaimedPages = claimedPagesDescriptor?.let(multiQueryResults::get) + + payoutTargets.mapNotNull { payoutTarget -> + val key = payoutTarget.era to payoutTarget.validatorStash + + val prefs = validatorsPrefs[key] ?: return@mapNotNull null + val claimedLegacyRewardsForValidator = claimedLegacyRewards[payoutTarget.validatorStash] ?: return@mapNotNull null + val claimedPages = allClaimedPages?.get(key).orEmpty() + + key to ValidatorEraClaim(prefs, claimedLegacyRewardsForValidator, payoutTarget.era, claimedPages) + }.toMap() + } } + // Nominator only pays out its own reward page private fun calculateNominatorReward( nominatorAccountId: AccountId, rewardCalculationContext: RewardCalculationContext - ): BigInteger? = with(rewardCalculationContext) { - val nominatorIdHex = nominatorAccountId.toHexString() + ): Payout? = with(rewardCalculationContext) { + val nominatorEraInfo = rewardCalculationContext.eraStake.findNominatorInfo(nominatorAccountId) ?: return null + val nominatorPage = nominatorEraInfo.pageNumber - val nominatorStakeInEra = validatorEraStats.exposure.others.firstOrNull { - it.who.toHexString() == nominatorIdHex - }?.value?.toDouble() ?: return null + if (rewardCalculationContext.eraClaim.pageClaimed(nominatorPage)) return null // already did the payout - val validatorTotalStake = validatorEraStats.exposure.total.toDouble() - val validatorCommission = validatorEraStats.prefs.commission.toDouble() + val nominatorStakeInEra = nominatorEraInfo.stake.toDouble() - val validatorTotalReward = calculateValidatorTotalReward(totalEraReward, eraValidatorPointsDistribution, validatorAccountId) ?: return null + val validatorTotalStake = eraStake.totalStake.toDouble() + val validatorCommission = eraClaim.validatorPrefs.commission.toDouble() - val nominatorReward = validatorTotalReward * (1 - validatorCommission) * (nominatorStakeInEra / validatorTotalStake) + val validatorTotalReward = calculateValidatorTotalReward(totalEraReward, eraPoints, eraStake.stash.value) ?: return null - return nominatorReward.toBigDecimal().toBigInteger() + val nominatorReward = validatorTotalReward * (1 - validatorCommission) * (nominatorStakeInEra / validatorTotalStake) + val nominatorRewardPlanks = nominatorReward.toBigDecimal().toBigInteger() + + Payout( + validatorStash = rewardCalculationContext.eraStake.stash, + era = rewardCalculationContext.eraStake.era, + amount = nominatorRewardPlanks, + pagesToClaim = listOf(nominatorPage) + ) } + // Validator pays out whole payout private fun calculateValidatorReward( rewardCalculationContext: RewardCalculationContext - ): BigInteger? = with(rewardCalculationContext) { - val validatorTotalStake = validatorEraStats.exposure.total.toDouble() - val validatorOwnStake = validatorEraStats.exposure.own.toDouble() - val validatorCommission = validatorEraStats.prefs.commission.toDouble() + ): Payout? = with(rewardCalculationContext) { + val totalPayoutPages = eraStake.totalPages + val unpaidPages = eraClaim.remainingPagesToClaim(totalPayoutPages) - val validatorTotalReward = calculateValidatorTotalReward(totalEraReward, eraValidatorPointsDistribution, validatorAccountId) ?: return null + if (unpaidPages.isEmpty()) return null + + val validatorTotalStake = eraStake.totalStake.toDouble() + val validatorOwnStake = eraStake.validatorStake.toDouble() + val validatorCommission = eraClaim.validatorPrefs.commission.toDouble() + + val validatorTotalReward = calculateValidatorTotalReward(totalEraReward, eraPoints, eraStake.stash.value) ?: return null val validatorRewardFromCommission = validatorTotalReward * validatorCommission val validatorRewardFromStake = validatorTotalReward * (1 - validatorCommission) * (validatorOwnStake / validatorTotalStake) val validatorOwnReward = validatorRewardFromStake + validatorRewardFromCommission - - validatorOwnReward.toBigDecimal().toBigInteger() + val validatorRewardPlanks = validatorOwnReward.toBigDecimal().toBigInteger() + + Payout( + validatorStash = rewardCalculationContext.eraStake.stash, + era = rewardCalculationContext.eraStake.era, + amount = validatorRewardPlanks, + pagesToClaim = unpaidPages + ) } private fun calculateValidatorTotalReward( @@ -166,112 +295,253 @@ class PayoutRepository( eraValidatorPointsDistribution: EraRewardPoints, validatorAccountId: AccountId, ): Double? { - val validatorIdHex = validatorAccountId.toHexString() - val totalMinted = totalEraReward.toDouble() val totalPoints = eraValidatorPointsDistribution.totalPoints.toDouble() val validatorPoints = eraValidatorPointsDistribution.individual.firstOrNull { - it.accountId.toHexString() == validatorIdHex + it.accountId.contentEquals(validatorAccountId) }?.rewardPoints?.toDouble() ?: return null return totalMinted * validatorPoints / totalPoints } private suspend fun getValidatorHistoricalStats( + chainId: ChainId, runtime: RuntimeSnapshot, - socketService: SocketService, historicalRange: List, - validatorAddresses: List, - ): List { - val stakingModule = runtime.metadata.staking() - - val exposureClippedStorage = stakingModule.storage("ErasStakersClipped") - val exposureKeyMapping = validatorAddresses.associateWith { validatorAddress -> - historicalRange.associateWith { era -> - exposureClippedStorage.storageKey(runtime, era, validatorAddress.toAccountId()) - } + payoutTargets: List, + ): List { + val (legacyEras, _) = partitionHistoricalRangeByPagedExposurePresence(historicalRange, runtime, chainId) + val legacyEraSet = legacyEras.toSet() + + val (legacyPayoutTargets, pagedPayoutTargets) = payoutTargets.partition { it.era in legacyEraSet } + + return getLegacyValidatorEraStake(chainId, legacyPayoutTargets) + getPagedValidatorHistoricalStats(chainId, pagedPayoutTargets) + } + + private suspend fun getPagedValidatorHistoricalStats( + chainId: ChainId, + payoutTargets: List, + ): List { + if (payoutTargets.isEmpty()) return emptyList() + + val pagedExposures = remoteStorage.fetchPagedExposures(chainId, payoutTargets) + + return pagedExposures.mapNotNull { (key, pagedExposure) -> + if (pagedExposure == null) return@mapNotNull null + + val (era, accountId) = key + PagedValidatorEraStake(accountId, era, pagedExposure) + }.also { + Log.d("PayoutRepository", "Fetched getPagedValidatorHistoricalStats for ${payoutTargets.size} targets") } + } - val validatorPrefsStorage = stakingModule.storage("ErasValidatorPrefs") - val prefsKeyMapping = validatorAddresses.associateWith { validatorAddress -> - historicalRange.associateWith { era -> - validatorPrefsStorage.storageKey(runtime, era, validatorAddress.toAccountId()) + private suspend fun StorageDataSource.fetchPagedExposures( + chainId: ChainId, + payoutTargets: List + ): Map, PagedExposure?> { + return query(chainId) { + val eraAndValidatorKeys = payoutTargets.map { listOf(it.era, it.validatorStash.value) } + + val overview = runtime.metadata.staking().storage("ErasStakersOverview").entries( + keysArguments = eraAndValidatorKeys, + keyExtractor = { (era: EraIndex, validatorStash: AccountId) -> era to validatorStash.intoKey() }, + binding = { decoded, _ -> decoded?.let { bindExposureOverview(decoded) } }, + ) + + val pageKeys = overview.flatMap { (key, exposureOverview) -> + if (exposureOverview == null) return@flatMap emptyList() + + (0 until exposureOverview.pageCount.toInt()).map { page -> + listOf(key.first, key.second.value, page.toBigInteger()) + } } - } - val controllerStorage = stakingModule.storage("Bonded") - val controllerMapping = validatorAddresses.associateWith { validatorAddress -> - controllerStorage.storageKey(runtime, validatorAddress.toAccountId()) + val pages = runtime.metadata.staking().storage("ErasStakersPaged").entries( + keysArguments = pageKeys, + keyExtractor = { (era: EraIndex, validatorStash: AccountId, page: BigInteger) -> Triple(era, validatorStash.intoKey(), page.toInt()) }, + binding = { decoded, _ -> decoded?.let { bindExposurePage(decoded) } }, + ) + + mergeIntoPagedExposure(overview, pages) } + } - val exposureClippedKeys = exposureKeyMapping.values.map(Map::values).flatten() - val prefsKeys = prefsKeyMapping.values.map(Map::values).flatten() - val ledgerKeys = controllerMapping.values.toList() + private fun mergeIntoPagedExposure( + overviews: Map, ExposureOverview?>, + pages: Map, ExposurePage?> + ): Map, PagedExposure?> { + return overviews.mapValues { (key, exposureOverview) -> + if (exposureOverview == null) return@mapValues null - val allResults = bulkRetriever.queryKeys(socketService, exposureClippedKeys + prefsKeys + ledgerKeys) + val totalPages = exposureOverview.pageCount.toInt() + val exposurePages = (0 until totalPages).map { page -> + pages[Triple(key.first, key.second, page)] ?: return@mapValues null + } - val ledgerStorage = stakingModule.storage("Ledger") - val ledgerKeysMapping = controllerMapping.mapValuesNotNull { (_, key) -> - allResults[key]?.fromHex()?.let { ledgerStorage.storageKey(runtime, it) } + PagedExposure(exposureOverview, exposurePages) } + } - val ledgerResults = bulkRetriever.queryKeys(socketService, ledgerKeysMapping.values.toList()) + private suspend fun getLegacyValidatorEraStake( + chainId: ChainId, + payoutTargets: List, + ): List { + if (payoutTargets.isEmpty()) return emptyList() - return validatorAddresses.mapNotNull { validatorAddress -> - val ledger = ledgerResults[ledgerKeysMapping[validatorAddress]]?.let { bindStakingLedger(it, runtime) } ?: return@mapNotNull null + return remoteStorage.query(chainId) { + val eraAndValidatorKeys = payoutTargets.map { listOf(it.era, it.validatorStash.value) } - val historicalStats = historicalRange.associateWith { era -> - val exposureKey = exposureKeyMapping[validatorAddress]!![era]!! - val prefsKey = prefsKeyMapping[validatorAddress]!![era]!! + val exposuresClipped = runtime.metadata.staking().storage("ErasStakersClipped").entries( + keysArguments = eraAndValidatorKeys, + keyExtractor = { (era: EraIndex, validatorStash: AccountId) -> era to validatorStash.intoKey() }, + binding = { decoded, _ -> decoded?.let { bindExposure(decoded) } }, + ) - val exposure = allResults[exposureKey]?.let { bindExposure(it, runtime) } - val prefs = allResults[prefsKey]?.let { bindValidatorPrefs(it, runtime) } + payoutTargets.mapNotNull { payoutTarget -> + val eraAndAccountIdKey = payoutTarget.era to payoutTarget.validatorStash - if (exposure != null && prefs != null) { - ValidatorHistoricalStats.ValidatorEraStats(prefs, exposure) - } else { - null - } - } + val exposure = exposuresClipped[eraAndAccountIdKey] ?: return@mapNotNull null - ValidatorHistoricalStats(validatorAddress, ledger, historicalStats) + LegacyValidatorEraStake(payoutTarget.validatorStash, payoutTarget.era, exposure) + }.also { + Log.d("PayoutRepository", "Fetched LegacyValidatorEraStake for ${payoutTargets.size} targets") + } } } private suspend fun retrieveEraPointsDistribution( chainId: ChainId, runtime: RuntimeSnapshot, - historicalRange: List, + eras: List, ): HistoricalMapping { val storage = runtime.metadata.staking().storage("ErasRewardPoints") - return retrieveHistoricalInfo(chainId, runtime, historicalRange, storage, ::bindEraRewardPoints) + return retrieveHistoricalInfoForValidator(chainId, eras, storage, ::bindEraRewardPoints) } private suspend fun retrieveTotalEraReward( chainId: ChainId, runtime: RuntimeSnapshot, - historicalRange: List, + eras: List, ): HistoricalMapping { val storage = runtime.metadata.staking().storage("ErasValidatorReward") - return retrieveHistoricalInfo(chainId, runtime, historicalRange, storage, ::bindTotalValidatorEraReward) + return retrieveHistoricalInfoForValidator(chainId, eras, storage, ::bindNumber) } - private suspend fun retrieveHistoricalInfo( + private suspend fun retrieveHistoricalInfoForValidator( chainId: ChainId, - runtime: RuntimeSnapshot, - historicalRange: List, + eras: List, storage: StorageEntry, - binder: BinderWithType, + binding: DynamicInstanceBinder, ): HistoricalMapping { - val historicalKeysMapping = historicalRange.associateBy { storage.storageKey(runtime, it) } - val storageReturnType = storage.returnType() + return remoteStorage.query(chainId) { + storage.entries( + keysArguments = eras.wrapSingleArgumentKeys(), + keyExtractor = { (era: EraIndex) -> era }, + binding = { decoded, _ -> binding(decoded) } + ) + } + } + + private data class PartitionedHistoricalRange( + val legacy: List, + val paged: List + ) + + private class RewardCalculationContext( + val eraStake: ValidatorEraStake, + val eraClaim: ValidatorEraClaim, + val eraPoints: EraRewardPoints, + val totalEraReward: BigInteger + ) + + private interface ValidatorEraStake { + + class NominatorInfo(val stake: Balance, val pageNumber: Int) + + val stash: AccountIdKey - return storageCache.getEntries(historicalKeysMapping.keys.toList(), chainId) - .associate { - historicalKeysMapping[it.storageKey]!! to binder(it.content, runtime, storageReturnType) + val era: EraIndex + + val totalStake: BigInteger + + val totalPages: Int + + val validatorStake: BigInteger + + fun findNominatorInfo(accountId: AccountId): NominatorInfo? + } + + private class LegacyValidatorEraStake( + override val stash: AccountIdKey, + override val era: EraIndex, + private val exposure: Exposure + ) : ValidatorEraStake { + + override val totalPages: Int = 1 + + override val totalStake: BigInteger = exposure.total + + override val validatorStake: BigInteger = exposure.own + override fun findNominatorInfo(accountId: AccountId): NominatorInfo? { + val individualExposure = exposure.others.find { it.who.contentEquals(accountId) } + + return individualExposure?.let { + NominatorInfo(individualExposure.value, pageNumber = 0) + } + } + } + + private class PagedValidatorEraStake( + override val stash: AccountIdKey, + override val era: EraIndex, + private val exposure: PagedExposure, + ) : ValidatorEraStake { + + override val totalStake: BigInteger = exposure.overview.total + + override val validatorStake: BigInteger = exposure.overview.own + + override val totalPages: Int = exposure.overview.pageCount.toInt() + + override fun findNominatorInfo(accountId: AccountId): NominatorInfo? { + var result: NominatorInfo? = null + + exposure.pages.forEachIndexed { index, exposurePage -> + val individualExposure = exposurePage.others.find { it.who.contentEquals(accountId) } + + if (individualExposure != null) { + result = NominatorInfo(individualExposure.value, index) + } } + + return result + } + } + + private class ValidatorEraClaim( + val validatorPrefs: ValidatorPrefs, + legacyClaimedEras: Set, + era: EraIndex, + private val claimedPages: ClaimedRewardsPages + ) { + + private val claimedFromLegacy = era in legacyClaimedEras + + fun pageClaimed(page: Int): Boolean { + return claimedFromLegacy || page in claimedPages + } + + fun remainingPagesToClaim(totalPages: Int): List { + if (claimedFromLegacy) return emptyList() + + val allPages = (0 until totalPages).toSet() + val unpaidPages = allPages - claimedPages + + return unpaidPages.toList() + } } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingConstantsRepository.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingConstantsRepository.kt index e2acbae816..56da12aa67 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingConstantsRepository.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingConstantsRepository.kt @@ -24,7 +24,12 @@ class StakingConstantsRepository( ?: MAX_UNLOCK_CHUNKS_FALLBACK.toBigInteger() } - suspend fun maxRewardedNominatorPerValidator(chainId: ChainId): Int = getNumberConstant(chainId, "MaxNominatorRewardedPerValidator").toInt() + /** + * Returns maxRewardedNominatorPerValidator or null in case there is no limitation on rewarded nominators per validator + */ + suspend fun maxRewardedNominatorPerValidator(chainId: ChainId): Int? { + return getOptionalNumberConstant(chainId, "MaxNominatorRewardedPerValidator")?.toInt() + } suspend fun lockupPeriodInEras(chainId: ChainId): BigInteger = getNumberConstant(chainId, "BondingDuration") diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt index d61b41bba6..e3f3be6737 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt @@ -6,11 +6,16 @@ import io.novafoundation.nova.common.utils.constant import io.novafoundation.nova.common.utils.numberConstant import io.novafoundation.nova.common.utils.numberConstantOrNull import io.novafoundation.nova.common.utils.staking +import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core_db.dao.AccountStakingDao import io.novafoundation.nova.core_db.model.AccountStakingLocal import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.domain.model.EraIndex +import io.novafoundation.nova.feature_staking_api.domain.model.Exposure +import io.novafoundation.nova.feature_staking_api.domain.model.ExposureOverview +import io.novafoundation.nova.feature_staking_api.domain.model.ExposurePage +import io.novafoundation.nova.feature_staking_api.domain.model.IndividualExposure import io.novafoundation.nova.feature_staking_api.domain.model.Nominations import io.novafoundation.nova.feature_staking_api.domain.model.SlashingSpans import io.novafoundation.nova.feature_staking_api.domain.model.StakingLedger @@ -23,6 +28,8 @@ import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.api.st import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindActiveEra import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindCurrentEra import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindExposure +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindExposureOverview +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindExposurePage import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindHistoryDepth import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindMaxNominators import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindMinBond @@ -32,6 +39,7 @@ import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindin import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindSlashDeferDuration import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindSlashingSpans import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindValidatorPrefs +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.ValidatorExposureUpdater import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.activeEraStorageKey import io.novafoundation.nova.feature_staking_impl.data.repository.datasource.StakingStoriesDataSource import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstants @@ -66,6 +74,7 @@ class StakingRepositoryImpl( private val walletConstants: WalletConstants, private val chainRegistry: ChainRegistry, private val stakingStoriesDataSource: StakingStoriesDataSource, + private val storageCache: StorageCache, ) : StakingRepository { override suspend fun eraStartSessionIndex(chainId: ChainId, currentEra: BigInteger): EraIndex { @@ -109,7 +118,54 @@ class StakingRepositoryImpl( binding = { scale, runtime -> bindActiveEra(scale, runtime) } ) - override suspend fun getElectedValidatorsExposure(chainId: ChainId, eraIndex: EraIndex) = localStorage.query(chainId) { + override suspend fun getElectedValidatorsExposure(chainId: ChainId, eraIndex: EraIndex): AccountIdMap { + val isPagedExposures = isPagedExposuresUsed(chainId) + + return if (isPagedExposures) { + fetchPagedEraStakers(chainId, eraIndex) + } else { + fetchLegacyEraStakers(chainId, eraIndex) + } + } + + private suspend fun fetchPagedEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap = localStorage.query(chainId) { + val eraStakersOverview = metadata.staking().storage("ErasStakersOverview").entries( + eraIndex, + keyExtractor = { (_: BigInteger, accountId: ByteArray) -> accountId.toHexString() }, + binding = { instance, _ -> bindExposureOverview(instance) } + ) + + val eraStakersPaged = runtime.metadata.staking().storage("ErasStakersPaged").entries( + eraIndex, + keyExtractor = { (_: BigInteger, accountId: ByteArray, page: BigInteger) -> accountId.toHexString() to page.toInt() }, + binding = { instance, _ -> bindExposurePage(instance) } + ) + + mergeOverviewsAndPagedOthers(eraStakersOverview, eraStakersPaged) + } + + private fun mergeOverviewsAndPagedOthers( + eraStakerOverviews: AccountIdMap, + othersPaged: Map, ExposurePage> + ): AccountIdMap { + return eraStakerOverviews.mapValues { (accountId, overview) -> + // avoid unnecessary growth allocations by pre-allocating exact needed number of elements + val others = ArrayList(overview.nominatorCount.toInt()) + + (0 until overview.pageCount.toInt()).forEach { pageNumber -> + val page = othersPaged[accountId to pageNumber]?.others.orEmpty() + others.addAll(page) + } + + Exposure( + total = overview.total, + own = overview.own, + others = others + ) + } + } + + private suspend fun fetchLegacyEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap = localStorage.query(chainId) { runtime.metadata.staking().storage("ErasStakers").entries( eraIndex, keyExtractor = { (_: BigInteger, accountId: ByteArray) -> accountId.toHexString() }, @@ -270,14 +326,19 @@ class StakingRepositoryImpl( } } + private suspend fun isPagedExposuresUsed(chainId: ChainId): Boolean { + val isPagedExposuresValue = storageCache.getEntry(ValidatorExposureUpdater.STORAGE_KEY_PAGED_EXPOSURES, chainId) + + return ValidatorExposureUpdater.decodeIsPagedExposuresValue(isPagedExposuresValue.content) + } + private fun observeAccountValidatorPrefs(chainId: ChainId, stashId: AccountId): Flow { - return localStorage.observe( - chainId = chainId, - keyBuilder = { it.metadata.staking().storage("Validators").storageKey(it, stashId) }, - binder = { scale, runtime -> - scale?.let { bindValidatorPrefs(it, runtime) } - } - ) + return localStorage.subscribe(chainId) { + runtime.metadata.staking().storage("Validators").observe( + stashId, + binding = { decoded -> decoded?.let { bindValidatorPrefs(decoded) } } + ) + } } private fun observeAccountNominations(chainId: ChainId, stashId: AccountId): Flow { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureDependencies.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureDependencies.kt index c8495e2207..8703881f96 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureDependencies.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureDependencies.kt @@ -14,6 +14,7 @@ import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.mixin.hints.ResourcesHintsMixinFactory import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.multiResult.PartialRetriableMixin import io.novafoundation.nova.common.validation.ValidationExecutor import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core_db.dao.AccountStakingDao @@ -160,4 +161,6 @@ interface StakingFeatureDependencies { val locksRepository: BalanceLocksRepository val externalBalanceDao: ExternalBalanceDao + + val partialRetriableMixinFactory: PartialRetriableMixin.Factory } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureModule.kt index bc0b262540..d768208a63 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureModule.kt @@ -103,6 +103,7 @@ import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi import io.novafoundation.nova.runtime.di.LOCAL_STORAGE_SOURCE import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.network.rpc.RpcCalls import io.novafoundation.nova.runtime.repository.ChainStateRepository import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository import io.novafoundation.nova.runtime.state.SelectedAssetOptionSharedState @@ -155,13 +156,15 @@ class StakingFeatureModule { stakingStoriesDataSource: StakingStoriesDataSource, walletConstants: WalletConstants, chainRegistry: ChainRegistry, + storageCache: StorageCache ): StakingRepository = StakingRepositoryImpl( accountStakingDao = accountStakingDao, remoteStorage = remoteStorageSource, localStorage = localStorageSource, stakingStoriesDataSource = stakingStoriesDataSource, walletConstants = walletConstants, - chainRegistry = chainRegistry + chainRegistry = chainRegistry, + storageCache = storageCache ) @Provides @@ -329,13 +332,11 @@ class StakingFeatureModule { @Provides @FeatureScope fun provideRecommendationSettingsProviderFactory( - stakingConstantsRepository: StakingConstantsRepository, computationalCache: ComputationalCache, sharedState: StakingSharedState, chainRegistry: ChainRegistry, ) = RecommendationSettingsProviderFactory( computationalCache, - stakingConstantsRepository, chainRegistry, sharedState ) @@ -416,12 +417,8 @@ class StakingFeatureModule { @FeatureScope fun provideValidatorSetFetcher( stakingApi: StakingApi, - stakingRepository: StakingRepository, ): SubQueryValidatorSetFetcher { - return SubQueryValidatorSetFetcher( - stakingApi, - stakingRepository - ) + return SubQueryValidatorSetFetcher(stakingApi) } @Provides @@ -443,11 +440,11 @@ class StakingFeatureModule { fun providePayoutRepository( stakingRepository: StakingRepository, validatorSetFetcher: SubQueryValidatorSetFetcher, - @PayoutsBulkRetriever bulkRetriever: BulkRetriever, - storageCache: StorageCache, chainRegistry: ChainRegistry, + rpcCalls: RpcCalls, + @Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource ): PayoutRepository { - return PayoutRepository(stakingRepository, bulkRetriever, validatorSetFetcher, storageCache, chainRegistry) + return PayoutRepository(stakingRepository, validatorSetFetcher, chainRegistry, remoteStorageSource, rpcCalls) } @Provides diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/relaychain/RelaychainStakingUpdatersModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/relaychain/RelaychainStakingUpdatersModule.kt index 53e869c9b4..7b8aa8fe4e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/relaychain/RelaychainStakingUpdatersModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/relaychain/RelaychainStakingUpdatersModule.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_staking_impl.di.staking.relaychain import dagger.Module import dagger.Provides import io.novafoundation.nova.common.data.network.rpc.BulkRetriever +import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core_db.dao.AccountStakingDao @@ -86,11 +87,13 @@ class RelaychainStakingUpdatersModule { chainRegistry: ChainRegistry, @DefaultBulkRetriever bulkRetriever: BulkRetriever, storageCache: StorageCache, + scope: ActiveEraScope ) = ValidatorExposureUpdater( bulkRetriever, sharedState, chainRegistry, - storageCache + storageCache, + scope ) @Provides @@ -186,10 +189,9 @@ class RelaychainStakingUpdatersModule { fun provideHistoricalMediator( sharedState: StakingSharedState, chainRegistry: ChainRegistry, - @DefaultBulkRetriever bulkRetriever: BulkRetriever, - stakingRepository: StakingRepository, storageCache: StorageCache, activeEraScope: ActiveEraScope, + preferences: Preferences ) = HistoricalUpdateMediator( historicalUpdaters = listOf( HistoricalTotalValidatorRewardUpdater(), @@ -197,10 +199,9 @@ class RelaychainStakingUpdatersModule { ), stakingSharedState = sharedState, chainRegistry = chainRegistry, - bulkRetriever = bulkRetriever, - stakingRepository = stakingRepository, storageCache = storageCache, - scope = activeEraScope + scope = activeEraScope, + preferences = preferences ) @Provides diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt index 25677c65ed..9f2036aec7 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt @@ -1,7 +1,5 @@ package io.novafoundation.nova.feature_staking_impl.domain -import io.novafoundation.nova.common.address.AccountIdKey -import io.novafoundation.nova.common.address.intoKey import io.novafoundation.nova.common.utils.flowOfAll import io.novafoundation.nova.common.utils.isZero import io.novafoundation.nova.common.utils.sumByBigInteger @@ -10,7 +8,6 @@ import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentit import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.domain.model.Exposure -import io.novafoundation.nova.feature_staking_api.domain.model.IndividualExposure import io.novafoundation.nova.feature_staking_api.domain.model.RewardDestination import io.novafoundation.nova.feature_staking_api.domain.model.StakingAccount import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState @@ -18,7 +15,6 @@ import io.novafoundation.nova.feature_staking_impl.data.StakingOption import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.fullId import io.novafoundation.nova.feature_staking_impl.data.mappers.mapAccountToStakingAccount -import io.novafoundation.nova.feature_staking_impl.data.model.Payout import io.novafoundation.nova.feature_staking_impl.data.repository.PayoutRepository import io.novafoundation.nova.feature_staking_impl.data.repository.StakingConstantsRepository import io.novafoundation.nova.feature_staking_impl.data.repository.StakingRewardsRepository @@ -41,6 +37,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.runtime.ext.accountIdOf +import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.state.assetWithChain import io.novafoundation.nova.runtime.state.chain @@ -89,8 +86,8 @@ class StakingInteractor( val payouts = payoutRepository.calculateUnpaidPayouts(currentStakingState) - val allValidatorAddresses = payouts.map(Payout::validatorAddress).distinct() - val identityMapping = identityRepository.getIdentitiesFromAddresses(currentStakingState.chain, allValidatorAddresses) + val allValidatorStashes = payouts.map { it.validatorStash.value }.distinct() + val identityMapping = identityRepository.getIdentitiesFromIds(allValidatorStashes, chainId) val pendingPayouts = payouts.map { val erasLeft = remainingEras(createdAtEra = it.era, activeEraIndex, historyDepth) @@ -100,9 +97,12 @@ class StakingInteractor( val leftTime = calculator.calculateTillEraSet(destinationEra = it.era + historyDepth + ERA_OFFSET).toLong() val currentTimestamp = System.currentTimeMillis() with(it) { - val validatorIdentity = identityMapping[validatorAddress] + val validatorIdentity = identityMapping[validatorStash] - val validatorInfo = PendingPayout.ValidatorInfo(validatorAddress, validatorIdentity?.display) + val validatorInfo = PendingPayout.ValidatorInfo( + address = currentStakingState.chain.addressOf(validatorStash.value), + identityName = validatorIdentity?.display + ) PendingPayout( validatorInfo = validatorInfo, @@ -110,7 +110,8 @@ class StakingInteractor( amountInPlanks = amount, timeLeft = leftTime, timeLeftCalculatedAt = currentTimestamp, - closeToExpire = closeToExpire + closeToExpire = closeToExpire, + pagesToClaim = pagesToClaim ) } }.sortedBy { it.era } @@ -262,7 +263,7 @@ class StakingInteractor( stakingConstantsRepository.maxValidatorsPerNominator(stakingSharedState.chainId(), stake) } - suspend fun maxRewardedNominators(): Int = withContext(Dispatchers.Default) { + suspend fun maxRewardedNominators(): Int? = withContext(Dispatchers.Default) { stakingConstantsRepository.maxRewardedNominatorPerValidator(stakingSharedState.chainId()) } @@ -327,13 +328,14 @@ class StakingInteractor( private suspend fun activeNominators(chainId: ChainId, exposures: Collection): Int { val activeNominatorsPerValidator = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId) - return exposures.fold(mutableSetOf()) { acc, exposure -> - acc += exposure.others.sortedByDescending(IndividualExposure::value) - .take(activeNominatorsPerValidator) - .map { it.who.intoKey() } - - acc - }.size + return exposures.fold(0) { acc, exposure -> + val othersSize = exposure.others.size + acc + if (activeNominatorsPerValidator != null) { + othersSize.coerceAtMost(activeNominatorsPerValidator) + } else { + othersSize + } + } } private fun totalStake(exposures: Collection): BigInteger { @@ -350,7 +352,7 @@ class StakingInteractor( private class StatusResolutionContext( val activeEraInfo: ActiveEraInfo, val asset: Asset, - val rewardedNominatorsPerValidator: Int, + val rewardedNominatorsPerValidator: Int?, val activeStake: Balance, ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractorExt.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractorExt.kt index d7cc81b8da..6f2460d0e2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractorExt.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractorExt.kt @@ -24,7 +24,7 @@ val NominationStatus.isOversubscribed: Boolean fun nominationStatus( stashId: AccountId, exposures: Collection, - rewardedNominatorsPerValidator: Int + rewardedNominatorsPerValidator: Int? ): NominationStatus { val comparator = { accountId: IndividualExposure -> accountId.who.contentEquals(stashId) @@ -37,7 +37,9 @@ fun nominationStatus( return NominationStatus.NOT_PRESENT } - val willBeRewarded = validatorsWithOurStake.any { it.willAccountBeRewarded(stashId, rewardedNominatorsPerValidator) } + val willBeRewarded = rewardedNominatorsPerValidator == null || validatorsWithOurStake.any { + it.willAccountBeRewarded(stashId, rewardedNominatorsPerValidator) + } return if (willBeRewarded) NominationStatus.ACTIVE else NominationStatus.OVERSUBSCRIBED } @@ -63,7 +65,6 @@ fun minimumStake( exposures: Collection, minimumNominatorBond: BigInteger, bagListLocator: BagListLocator?, - maxNominatorsInValidator: Int, bagListScoreConverter: BagListScoreConverter, bagListSize: BigInteger?, maxElectingVoters: BigInteger? diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/alerts/AlertsInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/alerts/AlertsInteractor.kt index d392cda145..5a19f0b074 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/alerts/AlertsInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/alerts/AlertsInteractor.kt @@ -42,7 +42,7 @@ class AlertsInteractor( class AlertContext( val exposures: Map, val stakingState: StakingState, - val maxRewardedNominatorsPerValidator: Int, + val maxRewardedNominatorsPerValidator: Int?, val minRecommendedStake: Balance, val activeEra: BigInteger, val asset: Asset, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/StakingSharedComputation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/StakingSharedComputation.kt index 351de81bdc..e3e862338f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/StakingSharedComputation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/StakingSharedComputation.kt @@ -86,14 +86,12 @@ class StakingSharedComputation( val bagListScoreConverter = BagListScoreConverter.U128(totalIssuance) val maxElectingVoters = bagListRepository.maxElectingVotes(chainId) val bagListSize = bagListRepository.bagListSize(chainId) - val maxNominatorsInValidator = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId) electedExposuresWithActiveEraFlow(chainId, scope).map { (exposures, activeEraIndex) -> val minStake = minimumStake( exposures = exposures.values, minimumNominatorBond = minBond, bagListLocator = bagListLocator, - maxNominatorsInValidator = maxNominatorsInValidator, bagListScoreConverter = bagListScoreConverter, bagListSize = bagListSize, maxElectingVoters = maxElectingVoters diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/model/PendingPayoutsStatistics.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/model/PendingPayoutsStatistics.kt index 3c1ec92789..41121f15fb 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/model/PendingPayoutsStatistics.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/model/PendingPayoutsStatistics.kt @@ -14,6 +14,7 @@ data class PendingPayout( val timeLeft: Long, val timeLeftCalculatedAt: Long, val closeToExpire: Boolean, + val pagesToClaim: List, ) { class ValidatorInfo( val address: String, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/payout/PayoutInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/payout/PayoutInteractor.kt index 0d502a006a..8b4457cfdf 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/payout/PayoutInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/payout/PayoutInteractor.kt @@ -1,13 +1,19 @@ package io.novafoundation.nova.feature_staking_impl.domain.payout +import io.novafoundation.nova.common.utils.hasCall +import io.novafoundation.nova.common.utils.multiResult.RetriableMultiResult +import io.novafoundation.nova.common.utils.staking +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.model.Payout -import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.payoutStakers import io.novafoundation.nova.feature_staking_impl.domain.validations.payout.MakePayoutPayload import io.novafoundation.nova.runtime.ext.accountIdOf +import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus +import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder import io.novafoundation.nova.runtime.state.chain -import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAccountId +import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.math.BigInteger @@ -17,26 +23,63 @@ class PayoutInteractor( private val extrinsicService: ExtrinsicService ) { - suspend fun estimatePayoutFee(accountAddress: String, payouts: List): BigInteger { + suspend fun estimatePayoutFee(payouts: List): Fee { return withContext(Dispatchers.IO) { - extrinsicService.estimateFee(stakingSharedState.chain()) { - payouts.forEach { - payoutStakers(it.era, it.validatorAddress.toAccountId()) - } + extrinsicService.estimateMultiFee(stakingSharedState.chain()) { + payoutMultiple(payouts) } } } - suspend fun makePayouts(payload: MakePayoutPayload): Result { + suspend fun makePayouts(payload: MakePayoutPayload): RetriableMultiResult { return withContext(Dispatchers.IO) { val chain = stakingSharedState.chain() val accountId = chain.accountIdOf(payload.originAddress) + val origin = TransactionOrigin.WalletWithAccount(accountId) - extrinsicService.submitExtrinsicWithAnySuitableWallet(chain, accountId) { - payload.payoutStakersCalls.forEach { - payoutStakers(it.era, it.validatorAddress.toAccountId()) - } + extrinsicService.submitMultiExtrinsicAwaitingInclusion(chain, origin) { + payoutMultiple(payload.payouts) } } } + + private fun CallBuilder.payoutMultiple(payouts: List) { + payouts.forEach { payout -> + makePayout(payout) + } + } + + private fun CallBuilder.makePayout(payout: Payout) { + if (runtime.metadata.staking().hasCall("payout_stakers_by_page")) { + payout.pagesToClaim.onEach { page -> + payoutStakersByPage(payout.era, payout.validatorStash.value, page) + } + } else { + // paged payout is not present so we use regular one + payoutStakers(payout.era, payout.validatorStash.value) + } + } + + private fun CallBuilder.payoutStakers(era: BigInteger, validatorId: AccountId) { + addCall( + "Staking", + "payout_stakers", + mapOf( + "validator_stash" to validatorId, + "era" to era + ) + ) + } + + private fun CallBuilder.payoutStakersByPage(era: BigInteger, validatorId: AccountId, page: Int) { + addCall( + "Staking", + "payout_stakers_by_page", + mapOf( + "validator_stash" to validatorId, + "era" to era, + "page" to page.toBigInteger() + ) + ) + } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProvider.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProvider.kt index 97ddf5f5cb..1ae6ae3649 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProvider.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProvider.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow class RecommendationSettingsProvider( - maximumRewardedNominators: Int, private val runtimeSnapshot: RuntimeSnapshot, ) { @@ -23,7 +22,7 @@ class RecommendationSettingsProvider( private val customizableFilters = runtimeSnapshot.availableDependents( NotSlashedFilter, HasIdentityFilter, - NotOverSubscribedFilter(maximumRewardedNominators) + NotOverSubscribedFilter ) val allAvailableFilters = alwaysEnabledFilters + customizableFilters diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProviderFactory.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProviderFactory.kt index 0a634b09f8..6fcd06993b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProviderFactory.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/RecommendationSettingsProviderFactory.kt @@ -2,7 +2,6 @@ package io.novafoundation.nova.feature_staking_impl.domain.recommendations.setti import io.novafoundation.nova.common.data.memory.ComputationalCache import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState -import io.novafoundation.nova.feature_staking_impl.data.repository.StakingConstantsRepository import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.getRuntime import kotlinx.coroutines.CoroutineScope @@ -11,7 +10,6 @@ private const val SETTINGS_PROVIDER_KEY = "SETTINGS_PROVIDER_KEY" class RecommendationSettingsProviderFactory( private val computationalCache: ComputationalCache, - private val stakingConstantsRepository: StakingConstantsRepository, private val chainRegistry: ChainRegistry, private val sharedState: StakingSharedState, ) { @@ -20,10 +18,7 @@ class RecommendationSettingsProviderFactory( return computationalCache.useCache(SETTINGS_PROVIDER_KEY, scope) { val chainId = sharedState.chainId() - RecommendationSettingsProvider( - maximumRewardedNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId), - runtimeSnapshot = chainRegistry.getRuntime(chainId) - ) + RecommendationSettingsProvider(chainRegistry.getRuntime(chainId)) } } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/filters/NotOverSubscribedFilter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/filters/NotOverSubscribedFilter.kt index 123c111edc..8121cf76c2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/filters/NotOverSubscribedFilter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/filters/NotOverSubscribedFilter.kt @@ -3,18 +3,10 @@ package io.novafoundation.nova.feature_staking_impl.domain.recommendations.setti import io.novafoundation.nova.feature_staking_api.domain.model.Validator import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settings.RecommendationFilter -class NotOverSubscribedFilter( - private val maxSubscribers: Int -) : RecommendationFilter { +object NotOverSubscribedFilter : RecommendationFilter { override fun shouldInclude(model: Validator): Boolean { - val electedInfo = model.electedInfo - - return if (electedInfo != null) { - electedInfo.nominatorStakes.size < maxSubscribers - } else { - // inactive validators are considered as non-oversubscribed - true - } + // inactive validators are considered as non-oversubscribed + return model.electedInfo?.isOversubscribed ?: true } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/payout/MakePayoutPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/payout/MakePayoutPayload.kt index c6515988d7..af5d79dab0 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/payout/MakePayoutPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/payout/MakePayoutPayload.kt @@ -1,15 +1,13 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.payout +import io.novafoundation.nova.feature_staking_impl.data.model.Payout import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import java.math.BigDecimal -import java.math.BigInteger class MakePayoutPayload( val originAddress: String, val fee: BigDecimal, val totalReward: BigDecimal, val asset: Asset, - val payoutStakersCalls: List -) { - data class PayoutStakersPayload(val era: BigInteger, val validatorAddress: String) -} + val payouts: List +) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt index 2098f8399e..971d742164 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt @@ -64,7 +64,7 @@ class ValidatorProvider( ownStake = it.own, nominatorStakes = it.others, apy = rewardCalculator.getApyFor(accountIdHex), - isOversubscribed = it.others.size > maxNominators + isOversubscribed = maxNominators != null && it.others.size > maxNominators ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/current/CurrentValidatorsInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/current/CurrentValidatorsInteractor.kt index 7ac96bc1db..558a328eac 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/current/CurrentValidatorsInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/current/CurrentValidatorsInteractor.kt @@ -82,7 +82,7 @@ class CurrentValidatorsInteractor( val userNominationRank = userNominationIndex + 1 - val willBeRewarded = userNominationRank < maxRewardedNominators + val willBeRewarded = maxRewardedNominators == null || userNominationRank < maxRewardedNominators Status.Active(nomination = userIndividualExposure.value, willUserBeRewarded = willBeRewarded) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/mappers/Validator.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/mappers/Validator.kt index 8d6732ddb8..dc8c372ac2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/mappers/Validator.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/mappers/Validator.kt @@ -202,7 +202,7 @@ suspend fun mapValidatorDetailsParcelToValidatorDetailsModel( val nominatorsCount = stake.stakers.size val rewardsWithLabel = displayConfig.rewardSuffix.format(resourceManager, stake.rewards) - val formattedMaxStakers = displayConfig.rewardedStakersPerStakeTarget.format() + val formattedMaxStakers = displayConfig.rewardedStakersPerStakeTarget?.format() ValidatorStakeModel( status = ValidatorStakeModel.Status( @@ -214,7 +214,7 @@ suspend fun mapValidatorDetailsParcelToValidatorDetailsModel( totalStake = totalStakeModel, minimumStake = stake.minimumStake?.let { mapAmountToAmountModel(it, asset) }, nominatorsCount = nominatorsCount.format(), - maxNominations = resourceManager.getString(R.string.staking_nominations_rewarded_format, formattedMaxStakers), + maxNominations = formattedMaxStakers?.let { resourceManager.getString(R.string.staking_nominations_rewarded_format, it) }, apy = rewardsWithLabel ) ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutFragment.kt index 3196e9b0cb..8a6053ddc9 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutFragment.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutFragment.kt @@ -66,6 +66,7 @@ class ConfirmPayoutFragment : BaseFragment() { } override fun subscribe(viewModel: ConfirmPayoutViewModel) { + observeRetries(viewModel.partialRetriableMixin) setupExternalActions(viewModel) observeValidations(viewModel) observeRetries(viewModel) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt index 13feba29d0..248f50d0a2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt @@ -8,7 +8,7 @@ import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.base.TitleAndMessage import io.novafoundation.nova.common.mixin.api.Validatable import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.requireException +import io.novafoundation.nova.common.utils.multiResult.PartialRetriableMixin import io.novafoundation.nova.common.validation.ValidationExecutor import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.common.validation.progressConsumer @@ -17,13 +17,13 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.W import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState import io.novafoundation.nova.feature_staking_impl.R -import io.novafoundation.nova.feature_staking_impl.data.model.Payout import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor import io.novafoundation.nova.feature_staking_impl.domain.payout.PayoutInteractor import io.novafoundation.nova.feature_staking_impl.domain.validations.payout.MakePayoutPayload import io.novafoundation.nova.feature_staking_impl.domain.validations.payout.PayoutValidationFailure import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.payouts.confirm.model.ConfirmPayoutPayload +import io.novafoundation.nova.feature_staking_impl.presentation.payouts.model.mapPendingPayoutParcelToPayout import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.requireFee @@ -48,6 +48,7 @@ class ConfirmPayoutViewModel( private val resourceManager: ResourceManager, private val selectedAssetState: AnySelectedAssetOptionSharedState, walletUiUseCase: WalletUiUseCase, + partialRetriableMixinFactory: PartialRetriableMixin.Factory, ) : BaseViewModel(), ExternalActions.Presentation by externalActions, FeeLoaderMixin by feeLoaderMixin, @@ -60,7 +61,7 @@ class ConfirmPayoutViewModel( .filterIsInstance() .share() - private val payouts = payload.payouts.map { Payout(it.validatorInfo.address, it.era, it.amountInPlanks) } + private val payouts = payload.payouts.map(::mapPendingPayoutParcelToPayout) private val _showNextProgress = MutableLiveData(false) val showNextProgress: LiveData = _showNextProgress @@ -78,6 +79,8 @@ class ConfirmPayoutViewModel( } .shareInBackground() + val partialRetriableMixin = partialRetriableMixinFactory.create(scope = this) + init { loadFee() } @@ -104,9 +107,7 @@ class ConfirmPayoutViewModel( val accountAddress = stakingStateFlow.first().accountAddress val amount = asset.token.configuration.amountFromPlanks(payload.totalRewardInPlanks) - val payoutStakersPayloads = payouts.map { MakePayoutPayload.PayoutStakersPayload(it.era, it.validatorAddress) } - - val makePayoutPayload = MakePayoutPayload(accountAddress, fee, amount, asset, payoutStakersPayloads) + val makePayoutPayload = MakePayoutPayload(accountAddress, fee, amount, asset, payouts) validationExecutor.requireValid( validationSystem = validationSystem, @@ -122,24 +123,22 @@ class ConfirmPayoutViewModel( private fun sendTransaction(payload: MakePayoutPayload) = launch { val result = payoutInteractor.makePayouts(payload) - _showNextProgress.value = false - - if (result.isSuccess) { - showMessage(resourceManager.getString(R.string.make_payout_transaction_sent)) - - router.returnToStakingMain() - } else { - showError(result.requireException()) - } + partialRetriableMixin.handleMultiResult( + multiResult = result, + onSuccess = { + showMessage(resourceManager.getString(R.string.make_payout_transaction_sent)) + router.returnToStakingMain() + }, + progressConsumer = _showNextProgress.progressConsumer(), + onRetryCancelled = { router.back() } + ) } private fun loadFee() { - feeLoaderMixin.loadFee( - viewModelScope, + feeLoaderMixin.loadFeeV2( + coroutineScope = viewModelScope, feeConstructor = { - val address = stakingStateFlow.first().accountAddress - - payoutInteractor.estimatePayoutFee(address, payouts) + payoutInteractor.estimatePayoutFee(payouts) }, onRetryCancelled = ::backClicked ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/di/ConfirmPayoutModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/di/ConfirmPayoutModule.kt index 88ce3d0327..d3fd60bd45 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/di/ConfirmPayoutModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/di/ConfirmPayoutModule.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.multiResult.PartialRetriableMixin import io.novafoundation.nova.common.validation.ValidationExecutor import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase @@ -43,6 +44,7 @@ class ConfirmPayoutModule { resourceManager: ResourceManager, singleAssetSharedState: StakingSharedState, walletUiUseCase: WalletUiUseCase, + partialRetriableMixinFactory: PartialRetriableMixin.Factory, ): ViewModel { return ConfirmPayoutViewModel( interactor = interactor, @@ -56,7 +58,8 @@ class ConfirmPayoutModule { validationExecutor = validationExecutor, resourceManager = resourceManager, selectedAssetState = singleAssetSharedState, - walletUiUseCase = walletUiUseCase + walletUiUseCase = walletUiUseCase, + partialRetriableMixinFactory = partialRetriableMixinFactory ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/list/PayoutsListViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/list/PayoutsListViewModel.kt index e9134a2097..00785a2458 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/list/PayoutsListViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/list/PayoutsListViewModel.kt @@ -8,8 +8,6 @@ import io.novafoundation.nova.common.mixin.api.RetryPayload import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.inBackground -import io.novafoundation.nova.common.utils.requireException -import io.novafoundation.nova.common.utils.requireValue import io.novafoundation.nova.common.utils.singleReplaySharedFlow import io.novafoundation.nova.common.utils.withLoading import io.novafoundation.nova.feature_currency_api.presentation.formatters.formatAsCurrency @@ -81,10 +79,10 @@ class PayoutsListViewModel( launch { val result = interactor.calculatePendingPayouts(viewModelScope) - if (result.isSuccess) { - payoutsStatisticsFlow.emit(result.requireValue()) - } else { - val errorMessage = result.requireException().message ?: resourceManager.getString(R.string.common_undefined_error_message) + result.onSuccess { value -> + payoutsStatisticsFlow.emit(value) + }.onFailure { exception -> + val errorMessage = exception.message ?: resourceManager.getString(R.string.common_undefined_error_message) retryEvent.value = Event( RetryPayload( @@ -139,7 +137,8 @@ class PayoutsListViewModel( amountInPlanks = amountInPlanks, timeLeftCalculatedAt = timeLeftCalculatedAt, timeLeft = timeLeft, - closeToExpire = closeToExpire + closeToExpire = closeToExpire, + pagesToClaim = pagesToClaim ) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/model/PendingPayoutParcelable.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/model/PendingPayoutParcelable.kt index 4e6e2cebac..227ee13d36 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/model/PendingPayoutParcelable.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/model/PendingPayoutParcelable.kt @@ -1,6 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.presentation.payouts.model import android.os.Parcelable +import io.novafoundation.nova.common.address.intoKey +import io.novafoundation.nova.feature_staking_impl.data.model.Payout +import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAccountId import kotlinx.android.parcel.Parcelize import java.math.BigInteger @@ -12,6 +15,7 @@ class PendingPayoutParcelable( val timeLeftCalculatedAt: Long, val timeLeft: Long, val closeToExpire: Boolean, + val pagesToClaim: List ) : Parcelable { @Parcelize class ValidatorInfoParcelable( @@ -19,3 +23,14 @@ class PendingPayoutParcelable( val identityName: String?, ) : Parcelable } + +fun mapPendingPayoutParcelToPayout( + parcelPayoutParcelable: PendingPayoutParcelable +): Payout { + return Payout( + validatorStash = parcelPayoutParcelable.validatorInfo.address.toAccountId().intoKey(), + era = parcelPayoutParcelable.era, + amount = parcelPayoutParcelable.amountInPlanks, + pagesToClaim = parcelPayoutParcelable.pagesToClaim + ) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/details/StakeTargetDetailsPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/details/StakeTargetDetailsPayload.kt index 320380b6a2..abee71d5f0 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/details/StakeTargetDetailsPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/details/StakeTargetDetailsPayload.kt @@ -19,7 +19,7 @@ class StakeTargetDetailsPayload( @Parcelize class DisplayConfig( val rewardSuffix: RewardSuffix, - val rewardedStakersPerStakeTarget: Int, + val rewardedStakersPerStakeTarget: Int?, @StringRes val titleRes: Int, @StringRes val stakersLabelRes: Int, @StringRes val oversubscribedWarningText: Int, diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/amountChooser/AmountChooserMixin.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/amountChooser/AmountChooserMixin.kt index a4e738526d..16c05843cb 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/amountChooser/AmountChooserMixin.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/amountChooser/AmountChooserMixin.kt @@ -13,9 +13,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first import java.math.BigDecimal import java.math.BigInteger -import kotlinx.coroutines.flow.first typealias MaxClick = () -> Unit @@ -42,7 +42,6 @@ interface AmountChooserMixinBase : CoroutineScope { interface Presentation : AmountChooserMixinBase { - @Deprecated("Use amountState instead") val amount: Flow val amountState: Flow> diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt index df811d7781..8efbb44892 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt @@ -79,9 +79,9 @@ interface GenericFeeLoaderMixin : Retriable { @Deprecated( message = "Use `awaitDecimalFee` instead since it holds more information about fee", - replaceWith = ReplaceWith("awaitDecimalFee().decimalAmount") + replaceWith = ReplaceWith("awaitDecimalFee().networkFeeDecimalAmount") ) - suspend fun awaitFee(): BigDecimal = awaitDecimalFee().decimalAmount + suspend fun awaitFee(): BigDecimal = awaitDecimalFee().networkFeeDecimalAmount } interface Factory { @@ -264,3 +264,24 @@ fun FeeLoaderMixin.Presentation.connectWithV2( .inBackground() .launchIn(scope) } + +fun FeeLoaderMixin.Presentation.connectWithV2( + inputSource1: Flow, + inputSource2: Flow, + scope: CoroutineScope, + feeConstructor: suspend Token.(input1: I1, input2: I2) -> Fee, + onRetryCancelled: () -> Unit = {} +) { + combine( + inputSource1, + inputSource2 + ) { input1, input2 -> + loadFeeV2( + coroutineScope = scope, + feeConstructor = { feeConstructor(it, input1, input2) }, + onRetryCancelled = onRetryCancelled + ) + } + .inBackground() + .launchIn(scope) +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/network/rpc/RpcCalls.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/network/rpc/RpcCalls.kt index b365252405..fb18c8013d 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/network/rpc/RpcCalls.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/network/rpc/RpcCalls.kt @@ -9,11 +9,14 @@ import io.novafoundation.nova.common.data.network.runtime.calls.GetBlockHashRequ import io.novafoundation.nova.common.data.network.runtime.calls.GetBlockRequest import io.novafoundation.nova.common.data.network.runtime.calls.GetFinalizedHeadRequest import io.novafoundation.nova.common.data.network.runtime.calls.GetHeaderRequest +import io.novafoundation.nova.common.data.network.runtime.calls.GetStorageSize import io.novafoundation.nova.common.data.network.runtime.calls.NextAccountIndexRequest import io.novafoundation.nova.common.data.network.runtime.model.FeeResponse import io.novafoundation.nova.common.data.network.runtime.model.SignedBlock import io.novafoundation.nova.common.data.network.runtime.model.SignedBlock.Block.Header +import io.novafoundation.nova.common.utils.asGsonParsedNumber import io.novafoundation.nova.common.utils.extrinsicHash +import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi import io.novafoundation.nova.runtime.ext.feeViaRuntimeCall import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus @@ -141,6 +144,10 @@ class RpcCalls( return socketFor(chainId).executeAsync(GetBlockHashRequest(blockNumber), mapper = pojo().nonNull()) } + suspend fun getStorageSize(chainId: ChainId, storageKey: String): BigInteger { + return socketFor(chainId).executeAsync(GetStorageSize(storageKey)).result?.asGsonParsedNumber().orZero() + } + private suspend fun socketFor(chainId: ChainId) = chainRegistry.getSocket(chainId) private fun bindPartialFee(decoded: Any?): FeeResponse { diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/network/updaters/SingleChainUpdateSystem.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/network/updaters/SingleChainUpdateSystem.kt index 883565bceb..5f552f0b7c 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/network/updaters/SingleChainUpdateSystem.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/network/updaters/SingleChainUpdateSystem.kt @@ -26,7 +26,7 @@ abstract class SingleChainUpdateSystem( val updaters = getUpdaters(selectedOption) runUpdaters(chain, updaters) - }.shareIn(CoroutineScope(Dispatchers.Default), replay = 1, started = SharingStarted.WhileSubscribed()) + }.shareIn(CoroutineScope(Dispatchers.IO), replay = 1, started = SharingStarted.WhileSubscribed()) override fun start(): Flow = updateFlow } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/multi/MultiQueryBuilder.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/multi/MultiQueryBuilder.kt index a55409f6e0..a5a92b1c05 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/multi/MultiQueryBuilder.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/multi/MultiQueryBuilder.kt @@ -1,13 +1,40 @@ package io.novafoundation.nova.runtime.storage.source.multi +import io.novafoundation.nova.runtime.storage.source.query.DynamicInstanceBinder +import io.novafoundation.nova.runtime.storage.source.query.StorageKeyComponents import io.novafoundation.nova.runtime.storage.source.query.wrapSingleArgumentKeys import jp.co.soramitsu.fearless_utils.runtime.metadata.module.StorageEntry interface MultiQueryBuilder { - fun StorageEntry.queryKey(vararg args: Any?) + interface Descriptor { - fun StorageEntry.queryKeys(keysArgs: List>) + fun parseKey(key: String): K - fun StorageEntry.querySingleArgKeys(singleArgKeys: List) = queryKeys(singleArgKeys.wrapSingleArgumentKeys()) + fun parseValue(value: String?): V + } + + interface Result { + + operator fun get(descriptor: Descriptor): Map + } + + fun StorageEntry.queryKey( + vararg args: Any?, + binding: DynamicInstanceBinder + ): Descriptor + + fun StorageEntry.queryKeys( + keysArgs: List>, + keyExtractor: (StorageKeyComponents) -> K, + binding: DynamicInstanceBinder + ): Descriptor + + fun StorageEntry.querySingleArgKeys( + keysArgs: Iterable, + keyExtractor: (StorageKeyComponents) -> K, + binding: DynamicInstanceBinder + ): Descriptor = queryKeys(keysArgs.wrapSingleArgumentKeys(), keyExtractor, binding) } + +fun MultiQueryBuilder.Result.singleValueOf(descriptor: MultiQueryBuilder.Descriptor<*, V>): V = get(descriptor).values.first() diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/multi/MultiQueryBuilderImpl.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/multi/MultiQueryBuilderImpl.kt index dfbfe43aa1..295926cc1e 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/multi/MultiQueryBuilderImpl.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/multi/MultiQueryBuilderImpl.kt @@ -1,6 +1,11 @@ package io.novafoundation.nova.runtime.storage.source.multi +import io.novafoundation.nova.common.utils.splitKeyToComponents +import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder.Descriptor +import io.novafoundation.nova.runtime.storage.source.query.DynamicInstanceBinder +import io.novafoundation.nova.runtime.storage.source.query.StorageKeyComponents import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.fromHex import jp.co.soramitsu.fearless_utils.runtime.metadata.module.StorageEntry import jp.co.soramitsu.fearless_utils.runtime.metadata.storageKey import jp.co.soramitsu.fearless_utils.runtime.metadata.storageKeys @@ -9,19 +14,68 @@ class MultiQueryBuilderImpl( private val runtime: RuntimeSnapshot ) : MultiQueryBuilder { + private val descriptors: MutableMap, List> = mutableMapOf() private val keys: MutableMap> = mutableMapOf() - override fun StorageEntry.queryKey(vararg args: Any?) { - keysForEntry(this).add(storageKey(runtime, *args)) + override fun StorageEntry.queryKey( + vararg args: Any?, + binding: DynamicInstanceBinder + ): Descriptor { + val key = storageKey(runtime, *args) + + keysForEntry(this).add(key) + return registerDescriptor(listOf(key), this, keyExtractor = { it }, binding) + } + + override fun StorageEntry.queryKeys( + keysArgs: List>, + keyExtractor: (StorageKeyComponents) -> K, + binding: DynamicInstanceBinder + ): Descriptor { + val keys = storageKeys(runtime, keysArgs) + + keysForEntry(this).addAll(keys) + return registerDescriptor(keys, this, keyExtractor, binding) } - override fun StorageEntry.queryKeys(keysArgs: List>) { - keysForEntry(this).addAll(storageKeys(runtime, keysArgs)) + fun descriptors(): Map, List> { + return descriptors } - fun build(): Map> { + fun keys(): Map> { return keys } private fun keysForEntry(entry: StorageEntry) = keys.getOrPut(entry, ::mutableListOf) + + private fun registerDescriptor( + keys: List, + storageEntry: StorageEntry, + keyExtractor: (StorageKeyComponents) -> K, + binding: DynamicInstanceBinder + ): Descriptor { + val newDescriptor = RealDescriptor(storageEntry, keyExtractor, binding) + descriptors[newDescriptor] = keys + + return newDescriptor + } + + private inner class RealDescriptor( + private val storageEntry: StorageEntry, + private val keyExtractor: (StorageKeyComponents) -> K, + private val valueBinding: (decoded: Any?) -> V + ) : Descriptor { + override fun parseKey(key: String): K { + val keyComponents = storageEntry.splitKeyToComponents(runtime, key) + + return keyExtractor(keyComponents) + } + + override fun parseValue(value: String?): V { + val valueType = storageEntry.type.value!! + val decoded = value?.let { valueType.fromHex(runtime, value) } + + return valueBinding(decoded) + } + } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt index 16bb84c680..bf5b17b60e 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt @@ -6,7 +6,6 @@ import io.novafoundation.nova.common.data.network.runtime.binding.fromByteArrayO import io.novafoundation.nova.common.data.network.runtime.binding.fromHexOrIncompatible import io.novafoundation.nova.common.data.network.runtime.binding.incompatible import io.novafoundation.nova.common.utils.ComponentHolder -import io.novafoundation.nova.common.utils.splitKeyToComponents import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilderImpl @@ -189,22 +188,23 @@ abstract class BaseStorageQueryContext( } } - override suspend fun multi( + @Suppress("OVERRIDE_DEPRECATION", "OverridingDeprecatedMember") + override suspend fun multiInternal( builderBlock: MultiQueryBuilder.() -> Unit - ): Map> { - val keysByStorageEntry = MultiQueryBuilderImpl(runtime).apply(builderBlock).build() + ): MultiQueryBuilder.Result { + val builder = MultiQueryBuilderImpl(runtime).apply(builderBlock) - val keys = keysByStorageEntry.flatMap { (_, keys) -> keys } + val keys = builder.keys().flatMap { (_, keys) -> keys } val values = queryKeys(keys, at) - return keysByStorageEntry.mapValues { (storageEntry, keys) -> - val valueType = storageEntry.type.value!! - + val delegate = builder.descriptors().mapValues { (descriptor, keys) -> keys.associateBy( - keySelector = { key -> storageEntry.splitKeyToComponents(runtime, key) }, - valueTransform = { key -> values[key]?.let { valueType.fromHex(runtime, it) } } + keySelector = { key -> descriptor.parseKey(key) }, + valueTransform = { key -> descriptor.parseValue(values[key]) } ) } + + return MultiQueryResult(delegate) } override suspend fun Constant.getAs(binding: DynamicInstanceBinder): V { @@ -250,4 +250,12 @@ abstract class BaseStorageQueryContext( binding(decoded, key) } } + + @JvmInline + private value class MultiQueryResult(val delegate: Map, Map>) : MultiQueryBuilder.Result { + @Suppress("UNCHECKED_CAST") + override fun get(descriptor: MultiQueryBuilder.Descriptor): Map { + return delegate.getValue(descriptor) as Map + } + } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt index 7c5bdaf8ca..70e2dc1620 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/StorageQueryContext.kt @@ -9,6 +9,9 @@ import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Constant import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Module import jp.co.soramitsu.fearless_utils.runtime.metadata.module.StorageEntry import kotlinx.coroutines.flow.Flow +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract typealias StorageKeyComponents = ComponentHolder typealias DynamicInstanceBinder = (dynamicInstance: Any?) -> V @@ -75,9 +78,10 @@ interface StorageQueryContext { vararg keyArguments: Any? ): String? - suspend fun multi( + @Deprecated("Use multi for better smart-casting", replaceWith = ReplaceWith(expression = "multi(builderBlock)")) + suspend fun multiInternal( builderBlock: MultiQueryBuilder.() -> Unit - ): Map> + ): MultiQueryBuilder.Result // no keyExtractor short-cut suspend fun StorageEntry.entries( @@ -103,9 +107,19 @@ interface StorageQueryContext { suspend fun Constant.getAs(binding: DynamicInstanceBinder): V } -fun Map>.singleValueOf(storageEntry: StorageEntry) = getValue(storageEntry).values.first() +@Suppress("DEPRECATION") +@OptIn(ExperimentalContracts::class) +suspend fun StorageQueryContext.multi( + builderBlock: MultiQueryBuilder.() -> Unit +): MultiQueryBuilder.Result { + contract { + callsInPlace(builderBlock, InvocationKind.EXACTLY_ONCE) + } -fun Collection<*>.wrapSingleArgumentKeys(): List> = map(::listOf) + return multiInternal(builderBlock) +} + +fun Iterable<*>.wrapSingleArgumentKeys(): List> = map(::listOf) val StorageQueryContext.metadata: RuntimeMetadata get() = runtime.metadata From a0b953f315b59825ac8c100c87e6823855cd349d Mon Sep 17 00:00:00 2001 From: antonijzelinskij <107959809+antonijzelinskij@users.noreply.github.com> Date: Mon, 18 Dec 2023 14:41:37 +0500 Subject: [PATCH 043/100] Update NftUniquesIntegrationTest.kt (#1278) --- .../nova/NftUniquesIntegrationTest.kt | 105 ++++++++++-------- 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt index 59c3bff422..dfa7a1fd3f 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt @@ -15,6 +15,8 @@ import io.novafoundation.nova.runtime.di.RuntimeApi import io.novafoundation.nova.runtime.di.RuntimeComponent import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection +import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder +import io.novafoundation.nova.runtime.storage.source.query.multi import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.fearless_utils.runtime.metadata.storage @@ -88,60 +90,71 @@ class NftUniquesIntegrationTest { val classesIds = classesWithInstances.map { (collection, _) -> collection }.distinct() - val classMetadataStorage = runtime.metadata.uniques().storage("ClassMetadataOf") - val classStorage = runtime.metadata.uniques().storage("Class") - val instanceMetadataStorage = runtime.metadata.uniques().storage("InstanceMetadataOf") - val instanceDetailsStorage = runtime.metadata.uniques().storage("Asset") - - val multiQueryResults = multiInternal { - classMetadataStorage.querySingleArgKeys(classesIds) - classStorage.querySingleArgKeys(classesIds) - instanceMetadataStorage.queryKeys(classesWithInstances) - instanceDetailsStorage.queryKeys(classesWithInstances) - } + val classDetailsDescriptor: MultiQueryBuilder.Descriptor + val classMetadatasDescriptor: MultiQueryBuilder.Descriptor + val instancesDetailsDescriptor: MultiQueryBuilder.Descriptor, UniquesInstance.Details> + val instancesMetadataDescriptor: MultiQueryBuilder.Descriptor, UniquesInstance.Metadata?> + + val multiQueryResults = multi { + classDetailsDescriptor = runtime.metadata.uniques().storage("Class").querySingleArgKeys( + keysArgs = classesIds, + keyExtractor = { it.component1() }, + binding = { parsedValue -> + val classDetailsStruct = parsedValue.cast() + + UniquesClass.Details( + instances = classDetailsStruct.getTyped("instances"), + frozen = classDetailsStruct.getTyped("isFrozen") + ) + } + ) - val classDetails = multiQueryResults.getValue(classStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() } - .mapValues { (_, parsedValue) -> - val classDetailsStruct = parsedValue.cast() + classMetadatasDescriptor = runtime.metadata.uniques().storage("ClassMetadataOf").querySingleArgKeys( + keysArgs = classesIds, + keyExtractor = { it.component1() }, + binding = { parsedValue -> + parsedValue?.cast()?.let { classMetadataStruct -> + UniquesClass.Metadata( + deposit = classMetadataStruct.getTyped("deposit"), + data = bindString(classMetadataStruct["data"]) + ) + } + } + ) - UniquesClass.Details( - instances = classDetailsStruct.getTyped("instances"), - frozen = classDetailsStruct.getTyped("isFrozen") - ) - } + instancesDetailsDescriptor = runtime.metadata.uniques().storage("Asset").queryKeys( + keysArgs = classesWithInstances, + keyExtractor = { it.component1() to it.component2() }, + binding = { parsedValue -> + val instanceDetailsStruct = parsedValue.cast() - val classMetadatas = multiQueryResults.getValue(classMetadataStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() } - .mapValues { (_, parsedValue) -> - parsedValue?.cast()?.let { classMetadataStruct -> - UniquesClass.Metadata( - deposit = classMetadataStruct.getTyped("deposit"), - data = bindString(classMetadataStruct["data"]) + UniquesInstance.Details( + owner = chain.addressOf(bindAccountId(instanceDetailsStruct["owner"])), + frozen = bindBoolean(instanceDetailsStruct["isFrozen"]) ) } - } + ) - val instancesDetails = multiQueryResults.getValue(instanceDetailsStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() to keyComponents.component2() } - .mapValues { (_, parsedValue) -> - val instanceDetailsStruct = parsedValue.cast() + instancesMetadataDescriptor = runtime.metadata.uniques().storage("InstanceMetadataOf").queryKeys( + keysArgs = classesWithInstances, + keyExtractor = { it.component1() to it.component2() }, + binding = { parsedValue -> + parsedValue?.cast()?.let { + UniquesInstance.Metadata( + data = bindString(it["data"]) + ) + } + } + ) + } - UniquesInstance.Details( - owner = chain.addressOf(bindAccountId(instanceDetailsStruct["owner"])), - frozen = bindBoolean(instanceDetailsStruct["isFrozen"]) - ) - } + val classDetails = multiQueryResults[classDetailsDescriptor] - val instancesMetadatas = multiQueryResults.getValue(instanceMetadataStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() to keyComponents.component2() } - .mapValues { (_, parsedValue) -> - parsedValue?.cast()?.let { - UniquesInstance.Metadata( - data = bindString(it["data"]) - ) - } - } + val classMetadatas = multiQueryResults[classMetadatasDescriptor] + + val instancesDetails = multiQueryResults[instancesDetailsDescriptor] + + val instancesMetadatas = multiQueryResults[instancesMetadataDescriptor] val classes = classesIds.associateWith { classId -> UniquesClass( From e4122bcbeabffabae0a2020e4cb3c58f39930acb Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 19 Dec 2023 03:37:03 +0100 Subject: [PATCH 044/100] Wallet details refactoring and Proxied wallet details implementation --- .../nova/app/root/navigation/Navigator.kt | 4 +- .../main/res/navigation/main_nav_graph.xml | 2 +- .../nova/common/view/AlertView.kt | 22 +- common/src/main/res/layout/view_alert.xml | 27 +- common/src/main/res/values/strings.xml | 4 +- .../di/AccountFeatureModule.kt | 4 +- ...teractor.kt => WalletDetailsInteractor.kt} | 75 ++---- .../account/common/listing/ProxyFormatter.kt | 20 +- .../details/AccountDetailsViewModel.kt | 242 ------------------ ...lsFragment.kt => WalletDetailsFragment.kt} | 7 +- .../account/details/WalletDetailsViewModel.kt | 116 +++++++++ .../details/di/AccountDetailsComponent.kt | 4 +- .../details/di/AccountDetailsModule.kt | 50 +++- .../details/mixin/LedgerWalletDetailsMixin.kt | 69 +++++ .../mixin/ParitySignerWalletDetailsMixin.kt | 60 +++++ .../mixin/PolkadotVaultWalletDetailsMixin.kt | 59 +++++ .../mixin/ProxiedWalletDetailsMixin.kt | 72 ++++++ .../mixin/SecretsWalletDetailsMixin.kt | 50 ++++ .../details/mixin/WalletDetailsMixin.kt | 38 +++ .../mixin/WalletDetailsMixinFactory.kt | 46 ++++ .../mixin/WatchOnlyWalletDetailsMixin.kt | 60 +++++ .../details/mixin/common/AccountFormatter.kt | 66 +++++ .../details/mixin/common/WalletMixinCommon.kt | 58 +++++ .../account/details/model/AccountTypeAlert.kt | 3 +- .../CurrentStakeTargetsFragment.kt | 2 +- .../details/ValidatorDetailsFragment.kt | 2 +- .../confirmation/SwapConfirmationFragment.kt | 4 +- .../options/SwapOptionsFragment.kt | 4 +- .../WalletConnectApproveSessionFragment.kt | 6 +- 29 files changed, 835 insertions(+), 341 deletions(-) rename feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/details/{AccountDetailsInteractor.kt => WalletDetailsInteractor.kt} (51%) delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsViewModel.kt rename feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/{AccountDetailsFragment.kt => WalletDetailsFragment.kt} (93%) create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsViewModel.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/LedgerWalletDetailsMixin.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ParitySignerWalletDetailsMixin.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/PolkadotVaultWalletDetailsMixin.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ProxiedWalletDetailsMixin.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/SecretsWalletDetailsMixin.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixin.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixinFactory.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WatchOnlyWalletDetailsMixin.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/AccountFormatter.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/WalletMixinCommon.kt diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt index 18cb6d83ad..f2be521fa2 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt @@ -17,7 +17,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.add.Impo import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.advancedEncryption.AdvancedEncryptionFragment import io.novafoundation.nova.feature_account_impl.presentation.account.advancedEncryption.AdvancedEncryptionModePayload -import io.novafoundation.nova.feature_account_impl.presentation.account.details.AccountDetailsFragment +import io.novafoundation.nova.feature_account_impl.presentation.account.details.WalletDetailsFragment import io.novafoundation.nova.feature_account_impl.presentation.exporting.ExportPayload import io.novafoundation.nova.feature_account_impl.presentation.exporting.json.confirm.ExportJsonConfirmFragment import io.novafoundation.nova.feature_account_impl.presentation.exporting.json.confirm.ExportJsonConfirmPayload @@ -410,7 +410,7 @@ class Navigator( } override fun openAccountDetails(metaId: Long) { - val extras = AccountDetailsFragment.getBundle(metaId) + val extras = WalletDetailsFragment.getBundle(metaId) navController?.navigate(R.id.action_open_account_details, extras) } diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index 1868cbc5aa..c3674fdc45 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -508,7 +508,7 @@ diff --git a/common/src/main/java/io/novafoundation/nova/common/view/AlertView.kt b/common/src/main/java/io/novafoundation/nova/common/view/AlertView.kt index f8e020e21a..3201c9c34a 100644 --- a/common/src/main/java/io/novafoundation/nova/common/view/AlertView.kt +++ b/common/src/main/java/io/novafoundation/nova/common/view/AlertView.kt @@ -3,20 +3,22 @@ package io.novafoundation.nova.common.view import android.content.Context import android.util.AttributeSet import android.view.View -import android.widget.LinearLayout import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.constraintlayout.widget.ConstraintLayout import io.novafoundation.nova.common.R import io.novafoundation.nova.common.utils.WithContextExtensions import io.novafoundation.nova.common.utils.getEnum import io.novafoundation.nova.common.utils.getResourceIdOrNull import io.novafoundation.nova.common.utils.letOrHide import io.novafoundation.nova.common.utils.setImageTintRes +import io.novafoundation.nova.common.utils.setTextOrHide import io.novafoundation.nova.common.utils.updatePadding import io.novafoundation.nova.common.utils.useAttributes import kotlinx.android.synthetic.main.view_alert.view.alertIcon import kotlinx.android.synthetic.main.view_alert.view.alertMessage +import kotlinx.android.synthetic.main.view_alert.view.alertSubMessage typealias SimpleAlertModel = String @@ -24,7 +26,7 @@ class AlertView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0, -) : LinearLayout(context, attrs, defStyle), WithContextExtensions by WithContextExtensions(context) { +) : ConstraintLayout(context, attrs, defStyle), WithContextExtensions by WithContextExtensions(context) { enum class StylePreset { WARNING, ERROR, INFO @@ -35,8 +37,6 @@ class AlertView @JvmOverloads constructor( init { View.inflate(context, R.layout.view_alert, this) - orientation = HORIZONTAL - updatePadding(top = 10.dp, start = 16.dp, end = 16.dp, bottom = 10.dp) attrs?.let(::applyAttrs) @@ -51,16 +51,20 @@ class AlertView @JvmOverloads constructor( setStyle(styleFromPreset(preset)) } - fun setText(text: String) { + fun setMessage(text: String) { alertMessage.text = text } - fun setText(@StringRes textRes: Int) { + fun setMessage(@StringRes textRes: Int) { alertMessage.setText(textRes) } + fun setSubMessage(text: CharSequence?) { + alertSubMessage.setTextOrHide(text) + } + fun setModel(maybeModel: SimpleAlertModel?) = letOrHide(maybeModel) { model -> - setText(model) + setMessage(model) } private fun setStyleBackground(@ColorRes colorRes: Int) { @@ -83,7 +87,7 @@ class AlertView @JvmOverloads constructor( setStyle(Style(iconRes, backgroundColorRes, iconTintRes)) val text = it.getString(R.styleable.AlertView_android_text) - text?.let(::setText) + text?.let(::setMessage) } private fun styleFromPreset(preset: StylePreset) = when (preset) { @@ -93,4 +97,4 @@ class AlertView @JvmOverloads constructor( } } -fun AlertView.setTextOrHide(text: String?) = letOrHide(text, ::setText) +fun AlertView.setMessageOrHide(text: String?) = letOrHide(text, ::setMessage) diff --git a/common/src/main/res/layout/view_alert.xml b/common/src/main/res/layout/view_alert.xml index 6c45231649..23927fb082 100644 --- a/common/src/main/res/layout/view_alert.xml +++ b/common/src/main/res/layout/view_alert.xml @@ -1,5 +1,6 @@ + tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> + + \ No newline at end of file diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 584297f67e..b355e42666 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,6 +1,8 @@ + This account has provided access to perform transactions to the following account: + Do not show this again This is Delegating (Proxied) account @@ -17,7 +19,7 @@ Access was revoked - %s proxy: + %s proxy Any Non Transfer Governance diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index dc447616ba..dddb70dbcd 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -77,7 +77,7 @@ import io.novafoundation.nova.feature_account_api.domain.account.identity.Identi import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity import io.novafoundation.nova.feature_account_impl.data.proxy.RealMetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_impl.di.modules.ProxySigningModule -import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountDetailsInteractor +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountTypePresentationMapper @@ -314,7 +314,7 @@ class AccountFeatureModule { accountRepository: AccountRepository, secretStoreV2: SecretStoreV2, chainRegistry: ChainRegistry, - ) = AccountDetailsInteractor( + ) = WalletDetailsInteractor( accountRepository, secretStoreV2, chainRegistry diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/details/AccountDetailsInteractor.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/details/WalletDetailsInteractor.kt similarity index 51% rename from feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/details/AccountDetailsInteractor.kt rename to feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/details/WalletDetailsInteractor.kt index 58d58d9809..192a63958a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/details/AccountDetailsInteractor.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/details/WalletDetailsInteractor.kt @@ -5,24 +5,20 @@ import io.novafoundation.nova.common.data.secrets.v2.entropy import io.novafoundation.nova.common.data.secrets.v2.getAccountSecrets import io.novafoundation.nova.common.data.secrets.v2.seed import io.novafoundation.nova.common.list.GroupedList -import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.addressIn import io.novafoundation.nova.feature_account_api.domain.model.hasChainAccountIn import io.novafoundation.nova.feature_account_api.presenatation.account.add.SecretType import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain.From -import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.SubstrateApplicationConfig -import io.novafoundation.nova.runtime.ext.defaultComparatorFrom import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext -class AccountDetailsInteractor( +class WalletDetailsInteractor( private val accountRepository: AccountRepository, private val secretStoreV2: SecretStoreV2, private val chainRegistry: ChainRegistry, @@ -36,28 +32,32 @@ class AccountDetailsInteractor( accountRepository.updateMetaAccountName(metaId, newName) } - suspend fun getChainProjections(metaAccount: MetaAccount): GroupedList = withContext(Dispatchers.Default) { - val chains = shownChainsFor(metaAccount.type) - - chains.map { chain -> - val address = metaAccount.addressIn(chain) - val accountId = metaAccount.accountIdIn(chain) - - val projection = if (address != null && accountId != null) { - AccountInChain.Projection(address, accountId) - } else { - null + suspend fun getChainProjections( + metaAccount: MetaAccount, + chains: List, + sorting: Comparator + ): GroupedList { + return withContext(Dispatchers.Default) { + chains.map { chain -> + val address = metaAccount.addressIn(chain) + val accountId = metaAccount.accountIdIn(chain) + + val projection = if (address != null && accountId != null) { + AccountInChain.Projection(address, accountId) + } else { + null + } + + AccountInChain( + chain = chain, + projection = projection, + from = if (metaAccount.hasChainAccountIn(chain.id)) From.CHAIN_ACCOUNT else From.META_ACCOUNT + ) } - - AccountInChain( - chain = chain, - projection = projection, - from = if (metaAccount.hasChainAccountIn(chain.id)) From.CHAIN_ACCOUNT else From.META_ACCOUNT - ) + .sortedWith(sorting) + .groupBy(AccountInChain::from) + .toSortedMap(compareBy(From::ordering)) } - .sortedWith(accountInChainComparator(metaAccount.type)) - .groupBy(AccountInChain::from) - .toSortedMap(compareBy(From::ordering)) } suspend fun availableExportTypes( @@ -75,29 +75,8 @@ class AccountDetailsInteractor( ) } - private val AccountInChain.hasChainAccount - get() = projection != null - - private fun accountInChainComparator(metaAccountType: LightMetaAccount.Type): Comparator { - val hasAccountOrdering: Comparator = when (metaAccountType) { - LightMetaAccount.Type.LEDGER -> compareBy { !it.hasChainAccount } - else -> compareBy { it.hasChainAccount } - } - - return hasAccountOrdering.then(Chain.defaultComparatorFrom(AccountInChain::chain)) - } - - private suspend fun shownChainsFor(metaAccountType: LightMetaAccount.Type): List { - val allChains = chainRegistry.currentChains.first() - - return when (metaAccountType) { - LightMetaAccount.Type.LEDGER -> { - val ledgerSupportedChainIds = SubstrateApplicationConfig.all().mapToSet { it.chainId } - - allChains.filter { it.id in ledgerSupportedChainIds } - } - else -> allChains - } + suspend fun getAllChains(): List { + return chainRegistry.currentChains.first() } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt index dab826b822..8203b81e22 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt @@ -23,18 +23,26 @@ class ProxyFormatter( proxyMetaAccount: MetaAccount, proxyAccount: ProxyAccount ): CharSequence { - val proxyType = mapProxyTypeToString(resourceManager, proxyAccount.proxyType) - val accountIconDrawable = walletUiUseCase.walletIcon(proxyMetaAccount, 16) + val proxyType = mapProxyTypeToString(proxyAccount.proxyType) + val formattedProxyMetaAccount = mapProxyMetaAccount(proxyMetaAccount) - return SpannableStringBuilder(resourceManager.getString(R.string.proxy_wallet_subtitle, proxyType)) + return SpannableStringBuilder(proxyType) + .append(":") .appendSpace() + .append(formattedProxyMetaAccount) + } + + suspend fun mapProxyMetaAccount(proxyMetaAccount: MetaAccount): CharSequence { + val accountIconDrawable = walletUiUseCase.walletIcon(proxyMetaAccount, 16) + + return SpannableStringBuilder() .appendEnd(drawableSpan(accountIconDrawable)) .appendSpace() .append(proxyMetaAccount.name, colorSpan(resourceManager.getColor(R.color.text_primary))) } - fun mapProxyTypeToString(resourceManager: ResourceManager, type: ProxyAccount.ProxyType): String { - return when (type) { + fun mapProxyTypeToString(type: ProxyAccount.ProxyType): String { + val proxyType = when (type) { ProxyAccount.ProxyType.Any -> resourceManager.getString(R.string.account_proxy_type_any) ProxyAccount.ProxyType.NonTransfer -> resourceManager.getString(R.string.account_proxy_type_any) ProxyAccount.ProxyType.Governance -> resourceManager.getString(R.string.account_proxy_type_governance) @@ -45,5 +53,7 @@ class ProxyFormatter( ProxyAccount.ProxyType.NominationPools -> resourceManager.getString(R.string.account_proxy_type_nomination_pools) is ProxyAccount.ProxyType.Other -> type.name.splitCamelCase().joinToString { it.capitalize() } } + + return resourceManager.getString(R.string.proxy_wallet_type, proxyType) } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsViewModel.kt deleted file mode 100644 index d759a6d6e9..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsViewModel.kt +++ /dev/null @@ -1,242 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.presentation.account.details - -import androidx.lifecycle.viewModelScope -import io.novafoundation.nova.common.address.AddressIconGenerator -import io.novafoundation.nova.common.base.BaseViewModel -import io.novafoundation.nova.common.list.headers.TextHeader -import io.novafoundation.nova.common.list.toListWithHeaders -import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.filterToSet -import io.novafoundation.nova.common.utils.flowOf -import io.novafoundation.nova.common.utils.inBackground -import io.novafoundation.nova.common.utils.invoke -import io.novafoundation.nova.common.view.AlertView -import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi -import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount -import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount.Type -import io.novafoundation.nova.feature_account_api.domain.model.asPolkadotVaultVariantOrThrow -import io.novafoundation.nova.feature_account_api.domain.model.isPolkadotVaultLike -import io.novafoundation.nova.feature_account_api.presenatation.account.add.SecretType -import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction -import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider -import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.formatWithPolkadotVaultLabel -import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions -import io.novafoundation.nova.feature_account_api.presenatation.mixin.importType.ImportTypeChooserMixin -import io.novafoundation.nova.feature_account_impl.R -import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountDetailsInteractor -import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain -import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter -import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert -import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi -import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherMixin -import io.novafoundation.nova.feature_account_impl.presentation.exporting.ExportPayload -import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.debounce -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlin.time.Duration.Companion.seconds - -private const val UPDATE_NAME_INTERVAL_SECONDS = 1L - -class AccountDetailsViewModel( - private val interactor: AccountDetailsInteractor, - private val accountRouter: AccountRouter, - private val iconGenerator: AddressIconGenerator, - private val resourceManager: ResourceManager, - private val metaId: Long, - private val externalActions: ExternalActions.Presentation, - private val chainRegistry: ChainRegistry, - private val importTypeChooserMixin: ImportTypeChooserMixin.Presentation, - private val addAccountLauncherMixin: AddAccountLauncherMixin.Presentation, - private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, -) : BaseViewModel(), - ExternalActions by externalActions, - ImportTypeChooserMixin by importTypeChooserMixin, - AddAccountLauncherMixin by addAccountLauncherMixin { - - val accountNameFlow: MutableStateFlow = MutableStateFlow("") - - private val metaAccount = async(Dispatchers.Default) { interactor.getMetaAccount(metaId) } - - val availableAccountActions = flowOf { - availableAccountActions(metaAccount().type) - }.shareInBackground() - - val typeAlert = flowOf { - accountTypeAlertFor(metaAccount().type) - }.shareInBackground() - - val chainAccountProjections = flowOf { interactor.getChainProjections(metaAccount()) } - .map { groupedList -> - groupedList.toListWithHeaders( - keyMapper = { type, _ -> mapFromToTextHeader(type) }, - valueMapper = { mapChainAccountProjectionToUi(metaAccount(), it) } - ) - } - .inBackground() - .share() - - init { - launch { - accountNameFlow.emit(metaAccount().name) - } - - syncNameChangesWithDb() - } - - fun backClicked() { - accountRouter.back() - } - - private fun syncNameChangesWithDb() { - accountNameFlow - .filter { it.isNotEmpty() } - .debounce(UPDATE_NAME_INTERVAL_SECONDS.seconds) - .onEach { interactor.updateName(metaId, it) } - .launchIn(viewModelScope) - } - - private suspend fun mapFromToTextHeader(from: AccountInChain.From): TextHeader? { - return when (metaAccount().type) { - Type.LEDGER, Type.PARITY_SIGNER, Type.POLKADOT_VAULT, Type.PROXIED -> null - Type.SECRETS, Type.WATCH_ONLY -> { - val resId = when (from) { - AccountInChain.From.META_ACCOUNT -> R.string.account_shared_secret - AccountInChain.From.CHAIN_ACCOUNT -> R.string.account_custom_secret - } - - return TextHeader(resourceManager.getString(resId)) - } - } - } - - private suspend fun mapChainAccountProjectionToUi(metaAccount: LightMetaAccount, accountInChain: AccountInChain) = with(accountInChain) { - val addressOrHint = when { - projection != null -> projection.address - metaAccount.type.isPolkadotVaultLike() -> { - val polkadotVaultVariant = metaAccount.type.asPolkadotVaultVariantOrThrow() - resourceManager.formatWithPolkadotVaultLabel(R.string.account_details_parity_signer_not_supported, polkadotVaultVariant) - } - - else -> resourceManager.getString(R.string.account_no_chain_projection) - } - - val accountIcon = projection?.let { - iconGenerator.createAddressIcon(it.accountId, AddressIconGenerator.SIZE_SMALL, backgroundColorRes = AddressIconGenerator.BACKGROUND_TRANSPARENT) - } ?: resourceManager.getDrawable(R.drawable.ic_warning_filled) - - val availableActionsForChain = availableActionsFor(accountInChain) - val canViewAddresses = accountInChain.projection != null - val canDoAnyActions = availableActionsForChain.isNotEmpty() || canViewAddresses - - AccountInChainUi( - chainUi = mapChainToUi(chain), - addressOrHint = addressOrHint, - address = projection?.address, - accountIcon = accountIcon, - actionsAvailable = canDoAnyActions - ) - } - - fun chainAccountClicked(item: AccountInChainUi) = launch { - if (!item.actionsAvailable) return@launch - - val chain = chainRegistry.getChain(item.chainUi.id) - - val type = ExternalActions.Type.Address(item.address) - - externalActions.showExternalActions(type, chain) - } - - fun exportClicked(inChain: Chain) = launch { - viewModelScope.launch { - val sources = interactor.availableExportTypes(metaAccount(), inChain) - - val payload = ImportTypeChooserMixin.Payload( - allowedTypes = sources, - onChosen = { exportTypeChosen(it, inChain) } - ) - importTypeChooserMixin.showChooser(payload) - } - } - - fun changeChainAccountClicked(inChain: Chain) { - launch { - addAccountLauncherMixin.initiateLaunch(inChain, metaAccount()) - } - } - - private fun availableAccountActions(accountType: Type): Set { - return when (accountType) { - Type.SECRETS -> setOf(AccountAction.EXPORT, AccountAction.CHANGE) - Type.WATCH_ONLY -> setOf(AccountAction.CHANGE) - Type.PARITY_SIGNER, Type.POLKADOT_VAULT, Type.PROXIED -> emptySet() - Type.LEDGER -> setOf(AccountAction.CHANGE) - } - } - - private fun accountTypeAlertFor(accountType: Type): AccountTypeAlert? { - return when (accountType) { - Type.WATCH_ONLY -> AccountTypeAlert( - style = AlertView.Style( - backgroundColorRes = R.color.block_background, - iconRes = R.drawable.ic_watch_only_filled - ), - text = resourceManager.getString(R.string.account_details_watch_only_alert) - ) - - Type.PARITY_SIGNER, Type.POLKADOT_VAULT -> { - val polkadotVaultVariant = accountType.asPolkadotVaultVariantOrThrow() - val variantConfig = polkadotVaultVariantConfigProvider.variantConfigFor(polkadotVaultVariant) - - AccountTypeAlert( - style = AlertView.Style( - backgroundColorRes = R.color.block_background, - iconRes = variantConfig.common.iconRes - ), - text = resourceManager.formatWithPolkadotVaultLabel(R.string.account_details_parity_signer_alert, polkadotVaultVariant) - ) - } - - Type.SECRETS -> null - Type.LEDGER -> AccountTypeAlert( - style = AlertView.Style( - backgroundColorRes = R.color.block_background, - iconRes = R.drawable.ic_ledger - ), - text = resourceManager.getString(R.string.ledger_wallet_details_description) - ) - - Type.PROXIED -> TODO() // Make a valid alert here - } - } - - private fun exportTypeChosen(type: SecretType, chain: Chain) { - val exportPayload = ExportPayload(metaId, chain.id) - - val navigationAction = when (type) { - SecretType.MNEMONIC -> accountRouter.exportMnemonicAction(exportPayload) - SecretType.SEED -> accountRouter.exportSeedAction(exportPayload) - SecretType.JSON -> accountRouter.exportJsonPasswordAction(exportPayload) - } - - accountRouter.withPinCodeCheckRequired(navigationAction) - } - - private suspend fun availableActionsFor(accountInChain: AccountInChain): Set { - return availableAccountActions.first().filterToSet { action -> - when (action) { - AccountAction.CHANGE -> true - AccountAction.EXPORT -> accountInChain.projection != null - } - } - } -} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsFragment.kt similarity index 93% rename from feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsFragment.kt rename to feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsFragment.kt index faebd16fff..84efcbe6d4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/AccountDetailsFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsFragment.kt @@ -30,7 +30,7 @@ import javax.inject.Inject private const val ACCOUNT_ID_KEY = "ACCOUNT_ADDRESS_KEY" -class AccountDetailsFragment : BaseFragment(), ChainAccountsAdapter.Handler { +class WalletDetailsFragment : BaseFragment(), ChainAccountsAdapter.Handler { @Inject lateinit var imageLoader: ImageLoader @@ -81,7 +81,7 @@ class AccountDetailsFragment : BaseFragment(), ChainAcc .inject(this) } - override fun subscribe(viewModel: AccountDetailsViewModel) { + override fun subscribe(viewModel: WalletDetailsViewModel) { setupExternalActions(viewModel) { context, payload -> ChainAccountActionsSheet( context, @@ -103,7 +103,8 @@ class AccountDetailsFragment : BaseFragment(), ChainAcc viewModel.typeAlert.observe { if (it != null) { accountDetailsTypeAlert.makeVisible() - accountDetailsTypeAlert.setText(it.text) + accountDetailsTypeAlert.setMessage(it.message) + accountDetailsTypeAlert.setSubMessage(it.subMessage) accountDetailsTypeAlert.setStyle(it.style) } else { accountDetailsTypeAlert.makeGone() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsViewModel.kt new file mode 100644 index 0000000000..46ee90ba75 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsViewModel.kt @@ -0,0 +1,116 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details + +import androidx.lifecycle.viewModelScope +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.utils.flowOfAll +import io.novafoundation.nova.common.utils.invoke +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount.Type +import io.novafoundation.nova.feature_account_api.presenatation.account.add.SecretType +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_account_api.presenatation.mixin.importType.ImportTypeChooserMixin +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.WalletDetailsMixinFactory +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi +import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherMixin +import io.novafoundation.nova.feature_account_impl.presentation.exporting.ExportPayload +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import kotlinx.coroutines.async +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.debounce +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlin.time.Duration.Companion.seconds + +private const val UPDATE_NAME_INTERVAL_SECONDS = 1L + +class WalletDetailsViewModel( + private val interactor: WalletDetailsInteractor, + private val accountRouter: AccountRouter, + private val metaId: Long, + private val externalActions: ExternalActions.Presentation, + private val chainRegistry: ChainRegistry, + private val importTypeChooserMixin: ImportTypeChooserMixin.Presentation, + private val addAccountLauncherMixin: AddAccountLauncherMixin.Presentation, + private val walletDetailsMixinFactory: WalletDetailsMixinFactory +) : BaseViewModel(), + ExternalActions by externalActions, + ImportTypeChooserMixin by importTypeChooserMixin, + AddAccountLauncherMixin by addAccountLauncherMixin { + + val walletDetailsMixin = async { walletDetailsMixinFactory.create(metaId) } + + val accountNameFlow: MutableStateFlow = MutableStateFlow("") + + val availableAccountActions = flowOfAll { walletDetailsMixin().availableAccountActions } + .shareInBackground() + + val typeAlert = flowOfAll { walletDetailsMixin().typeAlert } + .shareInBackground() + + val chainAccountProjections = flowOfAll { walletDetailsMixin().chainAccountProjections } + .shareInBackground() + + init { + launch { + accountNameFlow.emit(walletDetailsMixin().metaAccount.name) + } + + syncNameChangesWithDb() + } + + fun backClicked() { + accountRouter.back() + } + + private fun syncNameChangesWithDb() { + accountNameFlow + .filter { it.isNotEmpty() } + .debounce(UPDATE_NAME_INTERVAL_SECONDS.seconds) + .onEach { interactor.updateName(metaId, it) } + .launchIn(viewModelScope) + } + + fun chainAccountClicked(item: AccountInChainUi) = launch { + if (!item.actionsAvailable) return@launch + + val chain = chainRegistry.getChain(item.chainUi.id) + + val type = ExternalActions.Type.Address(item.address) + + externalActions.showExternalActions(type, chain) + } + + fun exportClicked(inChain: Chain) = launch { + viewModelScope.launch { + val sources = interactor.availableExportTypes(walletDetailsMixin().metaAccount, inChain) + + val payload = ImportTypeChooserMixin.Payload( + allowedTypes = sources, + onChosen = { exportTypeChosen(it, inChain) } + ) + importTypeChooserMixin.showChooser(payload) + } + } + + fun changeChainAccountClicked(inChain: Chain) { + launch { + addAccountLauncherMixin.initiateLaunch(inChain, walletDetailsMixin().metaAccount) + } + } + + private fun exportTypeChosen(type: SecretType, chain: Chain) { + val exportPayload = ExportPayload(metaId, chain.id) + + val navigationAction = when (type) { + SecretType.MNEMONIC -> accountRouter.exportMnemonicAction(exportPayload) + SecretType.SEED -> accountRouter.exportSeedAction(exportPayload) + SecretType.JSON -> accountRouter.exportJsonPasswordAction(exportPayload) + } + + accountRouter.withPinCodeCheckRequired(navigationAction) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/di/AccountDetailsComponent.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/di/AccountDetailsComponent.kt index 9d3b7b144d..00c1287c0b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/di/AccountDetailsComponent.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/di/AccountDetailsComponent.kt @@ -4,7 +4,7 @@ import androidx.fragment.app.Fragment import dagger.BindsInstance import dagger.Subcomponent import io.novafoundation.nova.common.di.scope.ScreenScope -import io.novafoundation.nova.feature_account_impl.presentation.account.details.AccountDetailsFragment +import io.novafoundation.nova.feature_account_impl.presentation.account.details.WalletDetailsFragment @Subcomponent( modules = [ @@ -23,5 +23,5 @@ interface AccountDetailsComponent { ): AccountDetailsComponent } - fun inject(fragment: AccountDetailsFragment) + fun inject(fragment: WalletDetailsFragment) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/di/AccountDetailsModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/di/AccountDetailsModule.kt index 453dea5a96..2d9585c5e7 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/di/AccountDetailsModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/di/AccountDetailsModule.kt @@ -14,41 +14,65 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_account_api.presenatation.mixin.importType.ImportTypeChooserMixin -import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountDetailsInteractor +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter -import io.novafoundation.nova.feature_account_impl.presentation.account.details.AccountDetailsViewModel +import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.ProxyFormatter +import io.novafoundation.nova.feature_account_impl.presentation.account.details.WalletDetailsViewModel +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.WalletDetailsMixinFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.AddAccountLauncherMixin import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @Module(includes = [ViewModelModule::class]) class AccountDetailsModule { + @Provides + fun provideAccountFormatterFactory( + resourceManager: ResourceManager, + @Caching iconGenerator: AddressIconGenerator, + ): AccountFormatterFactory { + return AccountFormatterFactory(iconGenerator, resourceManager) + } + + @Provides + fun provideWalletDetailsMixinFactory( + polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, + resourceManager: ResourceManager, + accountFormatterFactory: AccountFormatterFactory, + proxyFormatter: ProxyFormatter, + interactor: WalletDetailsInteractor + ): WalletDetailsMixinFactory { + return WalletDetailsMixinFactory( + polkadotVaultVariantConfigProvider, + resourceManager, + accountFormatterFactory, + proxyFormatter, + interactor + ) + } + @Provides @IntoMap - @ViewModelKey(AccountDetailsViewModel::class) + @ViewModelKey(WalletDetailsViewModel::class) fun provideViewModel( - interactor: AccountDetailsInteractor, + interactor: WalletDetailsInteractor, router: AccountRouter, - resourceManager: ResourceManager, - @Caching iconGenerator: AddressIconGenerator, metaId: Long, externalActions: ExternalActions.Presentation, chainRegistry: ChainRegistry, importTypeChooserMixin: ImportTypeChooserMixin.Presentation, addAccountLauncherMixin: AddAccountLauncherMixin.Presentation, - polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, + walletDetailsMixinFactory: WalletDetailsMixinFactory ): ViewModel { - return AccountDetailsViewModel( + return WalletDetailsViewModel( interactor = interactor, accountRouter = router, - iconGenerator = iconGenerator, - resourceManager = resourceManager, metaId = metaId, externalActions = externalActions, chainRegistry = chainRegistry, importTypeChooserMixin = importTypeChooserMixin, addAccountLauncherMixin = addAccountLauncherMixin, - polkadotVaultVariantConfigProvider = polkadotVaultVariantConfigProvider + walletDetailsMixinFactory = walletDetailsMixinFactory ) } @@ -56,7 +80,7 @@ class AccountDetailsModule { fun provideViewModelCreator( fragment: Fragment, viewModelFactory: ViewModelProvider.Factory - ): AccountDetailsViewModel { - return ViewModelProvider(fragment, viewModelFactory).get(AccountDetailsViewModel::class.java) + ): WalletDetailsViewModel { + return ViewModelProvider(fragment, viewModelFactory).get(WalletDetailsViewModel::class.java) } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/LedgerWalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/LedgerWalletDetailsMixin.kt new file mode 100644 index 0000000000..fee1580e81 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/LedgerWalletDetailsMixin.kt @@ -0,0 +1,69 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin + +import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.list.headers.TextHeader +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.common.utils.mapToSet +import io.novafoundation.nova.common.view.AlertView +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction +import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.baseAccountTitleFormatter +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.notHasAccountComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.withChainComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi +import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.SubstrateApplicationConfig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first + +class LedgerWalletDetailsMixin( + private val resourceManager: ResourceManager, + private val accountFormatterFactory: AccountFormatterFactory, + private val interactor: WalletDetailsInteractor, + metaAccount: MetaAccount +) : WalletDetailsMixin( + interactor, + metaAccount +) { + + private val accountFormatter = accountFormatterFactory.create(baseAccountTitleFormatter(resourceManager)) + + override val availableAccountActions: Flow> = flowOf { setOf(AccountAction.CHANGE) } + + override val typeAlert: Flow = flowOf { + AccountTypeAlert( + style = AlertView.Style( + backgroundColorRes = R.color.block_background, + iconRes = R.drawable.ic_ledger + ), + message = resourceManager.getString(R.string.ledger_wallet_details_description) + ) + } + + override suspend fun getChainProjections(): GroupedList { + val ledgerSupportedChainIds = SubstrateApplicationConfig.all().mapToSet { it.chainId } + val chains = interactor.getAllChains() + .filter { it.id in ledgerSupportedChainIds } + return interactor.getChainProjections( + metaAccount, + chains, + notHasAccountComparator().withChainComparator() + ) + } + + override suspend fun mapAccountHeader(from: AccountInChain.From): TextHeader? { + return null + } + + override suspend fun mapAccount(accountInChain: AccountInChain): AccountInChainUi { + return accountFormatter.formatChainAccountProjection( + accountInChain, + availableAccountActions.first() + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ParitySignerWalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ParitySignerWalletDetailsMixin.kt new file mode 100644 index 0000000000..68409a236b --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ParitySignerWalletDetailsMixin.kt @@ -0,0 +1,60 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin + +import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.list.headers.TextHeader +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.asPolkadotVaultVariantOrThrow +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction +import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.hasAccountComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.polkadotVaultAccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.polkadotVaultTitle +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.withChainComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first + +class ParitySignerWalletDetailsMixin( + private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, + private val resourceManager: ResourceManager, + private val accountFormatterFactory: AccountFormatterFactory, + private val interactor: WalletDetailsInteractor, + metaAccount: MetaAccount +) : WalletDetailsMixin( + interactor, + metaAccount +) { + + private val accountFormatter = accountFormatterFactory.create( + accountTitleFormatter = { it.polkadotVaultTitle(resourceManager, metaAccount) } + ) + + override val availableAccountActions: Flow> = flowOf { emptySet() } + + override val typeAlert: Flow = flowOf { + val vaultVariant = metaAccount.type.asPolkadotVaultVariantOrThrow() + val variantConfig = polkadotVaultVariantConfigProvider.variantConfigFor(vaultVariant) + polkadotVaultAccountTypeAlert(vaultVariant, variantConfig, resourceManager) + } + + override suspend fun getChainProjections(): GroupedList { + return interactor.getChainProjections(metaAccount, interactor.getAllChains(), hasAccountComparator().withChainComparator()) + } + + override suspend fun mapAccountHeader(from: AccountInChain.From): TextHeader? { + return null + } + + override suspend fun mapAccount(accountInChain: AccountInChain): AccountInChainUi { + return accountFormatter.formatChainAccountProjection( + accountInChain, + availableAccountActions.first() + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/PolkadotVaultWalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/PolkadotVaultWalletDetailsMixin.kt new file mode 100644 index 0000000000..35aa965051 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/PolkadotVaultWalletDetailsMixin.kt @@ -0,0 +1,59 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin + +import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.list.headers.TextHeader +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.asPolkadotVaultVariantOrThrow +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction +import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.hasAccountComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.polkadotVaultAccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.polkadotVaultTitle +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.withChainComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first + +class PolkadotVaultWalletDetailsMixin( + private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, + private val resourceManager: ResourceManager, + private val accountFormatterFactory: AccountFormatterFactory, + private val interactor: WalletDetailsInteractor, + metaAccount: MetaAccount +) : WalletDetailsMixin( + interactor, + metaAccount +) { + private val accountFormatter = accountFormatterFactory.create( + accountTitleFormatter = { it.polkadotVaultTitle(resourceManager, metaAccount) } + ) + + override val availableAccountActions: Flow> = flowOf { emptySet() } + + override val typeAlert: Flow = flowOf { + val vaultVariant = metaAccount.type.asPolkadotVaultVariantOrThrow() + val variantConfig = polkadotVaultVariantConfigProvider.variantConfigFor(vaultVariant) + polkadotVaultAccountTypeAlert(vaultVariant, variantConfig, resourceManager) + } + + override suspend fun getChainProjections(): GroupedList { + return interactor.getChainProjections(metaAccount, interactor.getAllChains(), hasAccountComparator().withChainComparator()) + } + + override suspend fun mapAccountHeader(from: AccountInChain.From): TextHeader? { + return null + } + + override suspend fun mapAccount(accountInChain: AccountInChain): AccountInChainUi { + return accountFormatter.formatChainAccountProjection( + accountInChain, + availableAccountActions.first() + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ProxiedWalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ProxiedWalletDetailsMixin.kt new file mode 100644 index 0000000000..86739c2512 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ProxiedWalletDetailsMixin.kt @@ -0,0 +1,72 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin + +import android.text.SpannableStringBuilder +import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.list.headers.TextHeader +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.appendSpace +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.common.view.AlertView +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction +import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.ProxyFormatter +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.baseAccountTitleFormatter +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.hasAccountComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.withChainComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first + +class ProxiedWalletDetailsMixin( + private val resourceManager: ResourceManager, + private val accountFormatterFactory: AccountFormatterFactory, + private val interactor: WalletDetailsInteractor, + private val proxyFormatter: ProxyFormatter, + metaAccount: MetaAccount +) : WalletDetailsMixin( + interactor, + metaAccount +) { + private val accountFormatter = accountFormatterFactory.create(baseAccountTitleFormatter(resourceManager)) + + override val availableAccountActions: Flow> = flowOf { emptySet() } + + override val typeAlert: Flow = flowOf { + val proxyAccount = metaAccount.proxy ?: return@flowOf null + val proxyMetaAccount = interactor.getMetaAccount(proxyAccount.metaId) + + AccountTypeAlert( + style = AlertView.Style( + backgroundColorRes = R.color.block_background, + iconRes = R.drawable.ic_proxy + ), + message = resourceManager.getString(R.string.proxied_wallet_details_info_warning), + subMessage = SpannableStringBuilder(proxyFormatter.mapProxyMetaAccount(proxyMetaAccount)) + .appendSpace() + .append(proxyFormatter.mapProxyTypeToString(proxyAccount.proxyType)) + ) + } + + override suspend fun getChainProjections(): GroupedList { + val proxiedChainIds = metaAccount.chainAccounts.keys + val chains = interactor.getAllChains() + .filter { it.id in proxiedChainIds } + return interactor.getChainProjections(metaAccount, chains, hasAccountComparator().withChainComparator()) + } + + override suspend fun mapAccountHeader(from: AccountInChain.From): TextHeader? { + return null + } + + override suspend fun mapAccount(accountInChain: AccountInChain): AccountInChainUi { + return accountFormatter.formatChainAccountProjection( + accountInChain, + availableAccountActions.first() + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/SecretsWalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/SecretsWalletDetailsMixin.kt new file mode 100644 index 0000000000..e57aa0582b --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/SecretsWalletDetailsMixin.kt @@ -0,0 +1,50 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin + +import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.list.headers.TextHeader +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.baseAccountTitleFormatter +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.hasAccountComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.mapToAccountHeader +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.withChainComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first + +class SecretsWalletDetailsMixin( + private val resourceManager: ResourceManager, + private val accountFormatterFactory: AccountFormatterFactory, + private val interactor: WalletDetailsInteractor, + metaAccount: MetaAccount +) : WalletDetailsMixin( + interactor, + metaAccount +) { + private val accountFormatter = accountFormatterFactory.create(baseAccountTitleFormatter(resourceManager)) + + override val availableAccountActions: Flow> = flowOf { setOf(AccountAction.EXPORT, AccountAction.CHANGE) } + + override val typeAlert: Flow = flowOf { null } + + override suspend fun getChainProjections(): GroupedList { + return interactor.getChainProjections(metaAccount, interactor.getAllChains(), hasAccountComparator().withChainComparator()) + } + + override suspend fun mapAccountHeader(from: AccountInChain.From): TextHeader? { + return from.mapToAccountHeader(resourceManager) + } + + override suspend fun mapAccount(accountInChain: AccountInChain): AccountInChainUi { + return accountFormatter.formatChainAccountProjection( + accountInChain, + availableAccountActions.first() + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixin.kt new file mode 100644 index 0000000000..c39bc2ccdb --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixin.kt @@ -0,0 +1,38 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin + +import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.list.headers.TextHeader +import io.novafoundation.nova.common.list.toListWithHeaders +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +abstract class WalletDetailsMixin( + private val interactor: WalletDetailsInteractor, + val metaAccount: MetaAccount +) { + + abstract val availableAccountActions: Flow> + + abstract val typeAlert: Flow + + val chainAccountProjections: Flow> = flowOf { getChainProjections() } + .map { groupedList -> + groupedList.toListWithHeaders( + keyMapper = { type, _ -> mapAccountHeader(type) }, + valueMapper = { mapAccount(it) } + ) + } + + abstract suspend fun getChainProjections(): GroupedList + + abstract suspend fun mapAccountHeader(from: AccountInChain.From): TextHeader? + + abstract suspend fun mapAccount(accountInChain: AccountInChain): AccountInChainUi +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixinFactory.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixinFactory.kt new file mode 100644 index 0000000000..97bd54a673 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixinFactory.kt @@ -0,0 +1,46 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin + +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount.Type +import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.ProxyFormatter +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory + +class WalletDetailsMixinFactory( + private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, + private val resourceManager: ResourceManager, + private val accountFormatterFactory: AccountFormatterFactory, + private val proxyFormatter: ProxyFormatter, + private val interactor: WalletDetailsInteractor +) { + + suspend fun create(metaId: Long): WalletDetailsMixin { + val metaAccount = interactor.getMetaAccount(metaId) + return when (metaAccount.type) { + Type.SECRETS -> SecretsWalletDetailsMixin(resourceManager, accountFormatterFactory, interactor, metaAccount) + + Type.WATCH_ONLY -> WatchOnlyWalletDetailsMixin(resourceManager, accountFormatterFactory, interactor, metaAccount) + + Type.LEDGER -> LedgerWalletDetailsMixin(resourceManager, accountFormatterFactory, interactor, metaAccount) + + Type.PARITY_SIGNER -> ParitySignerWalletDetailsMixin( + polkadotVaultVariantConfigProvider, + resourceManager, + accountFormatterFactory, + interactor, + metaAccount + ) + + Type.POLKADOT_VAULT -> PolkadotVaultWalletDetailsMixin( + polkadotVaultVariantConfigProvider, + resourceManager, + accountFormatterFactory, + interactor, + metaAccount + ) + + Type.PROXIED -> ProxiedWalletDetailsMixin(resourceManager, accountFormatterFactory, interactor, proxyFormatter, metaAccount) + } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WatchOnlyWalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WatchOnlyWalletDetailsMixin.kt new file mode 100644 index 0000000000..bf585c93a1 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WatchOnlyWalletDetailsMixin.kt @@ -0,0 +1,60 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin + +import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.list.headers.TextHeader +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.common.view.AlertView +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction +import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.baseAccountTitleFormatter +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.hasAccountComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.mapToAccountHeader +import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.withChainComparator +import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first + +class WatchOnlyWalletDetailsMixin( + private val resourceManager: ResourceManager, + private val accountFormatterFactory: AccountFormatterFactory, + private val interactor: WalletDetailsInteractor, + metaAccount: MetaAccount +) : WalletDetailsMixin( + interactor, + metaAccount +) { + private val accountFormatter = accountFormatterFactory.create(baseAccountTitleFormatter(resourceManager)) + + override val availableAccountActions: Flow> = flowOf { setOf(AccountAction.CHANGE) } + + override val typeAlert: Flow = flowOf { + AccountTypeAlert( + style = AlertView.Style( + backgroundColorRes = R.color.block_background, + iconRes = R.drawable.ic_watch_only_filled + ), + message = resourceManager.getString(R.string.account_details_watch_only_alert) + ) + } + + override suspend fun getChainProjections(): GroupedList { + return interactor.getChainProjections(metaAccount, interactor.getAllChains(), hasAccountComparator().withChainComparator()) + } + + override suspend fun mapAccountHeader(from: AccountInChain.From): TextHeader? { + return from.mapToAccountHeader(resourceManager) + } + + override suspend fun mapAccount(accountInChain: AccountInChain): AccountInChainUi { + return accountFormatter.formatChainAccountProjection( + accountInChain, + availableAccountActions.first() + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/AccountFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/AccountFormatter.kt new file mode 100644 index 0000000000..49bb99ba9c --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/AccountFormatter.kt @@ -0,0 +1,66 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common + +import io.novafoundation.nova.common.address.AddressIconGenerator +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.filterToSet +import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.* +import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi + +class AccountFormatterFactory( + private val iconGenerator: AddressIconGenerator, + private val resourceManager: ResourceManager +) { + + fun create(accountTitleFormatter: suspend (AccountInChain) -> String): AccountFormatter { + return AccountFormatter( + iconGenerator, + resourceManager, + accountTitleFormatter + ) + } +} + +class AccountFormatter( + private val iconGenerator: AddressIconGenerator, + private val resourceManager: ResourceManager, + private val accountTitleFormatter: suspend (AccountInChain) -> String +) { + + suspend fun formatChainAccountProjection(accountInChain: AccountInChain, availableActions: Set): AccountInChainUi { + return with(accountInChain) { + val accountIcon = projection?.let { + iconGenerator.createAddressIcon(it.accountId, AddressIconGenerator.SIZE_SMALL, backgroundColorRes = AddressIconGenerator.BACKGROUND_TRANSPARENT) + } ?: resourceManager.getDrawable(R.drawable.ic_warning_filled) + + val availableActionsForChain = availableActionsFor(accountInChain, availableActions) + val canViewAddresses = accountInChain.projection != null + val canDoAnyActions = availableActionsForChain.isNotEmpty() || canViewAddresses + + AccountInChainUi( + chainUi = mapChainToUi(chain), + addressOrHint = accountTitleFormatter(accountInChain), + address = projection?.address, + accountIcon = accountIcon, + actionsAvailable = canDoAnyActions + ) + } + } + + private suspend fun availableActionsFor(accountInChain: AccountInChain, availableActions: Set): Set { + return availableActions.filterToSet { action -> + when (action) { + AccountAction.CHANGE -> true + AccountAction.EXPORT -> accountInChain.projection != null + } + } + } +} + +fun baseAccountTitleFormatter(resourceManager: ResourceManager): (AccountInChain) -> String { + return { accountInChain -> + accountInChain.projection?.address ?: resourceManager.getString(R.string.account_no_chain_projection) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/WalletMixinCommon.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/WalletMixinCommon.kt new file mode 100644 index 0000000000..e72f332355 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/WalletMixinCommon.kt @@ -0,0 +1,58 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common + +import io.novafoundation.nova.common.list.headers.TextHeader +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.view.AlertView +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant +import io.novafoundation.nova.feature_account_api.domain.model.asPolkadotVaultVariantOrThrow +import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfig +import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.formatWithPolkadotVaultLabel +import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain +import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert +import io.novafoundation.nova.runtime.ext.defaultComparatorFrom +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain + +fun polkadotVaultAccountTypeAlert( + pokadotVaultVariant: PolkadotVaultVariant, + variantConfig: PolkadotVaultVariantConfig, + resourceManager: ResourceManager +): AccountTypeAlert { + return AccountTypeAlert( + style = AlertView.Style( + backgroundColorRes = R.color.block_background, + iconRes = variantConfig.common.iconRes + ), + message = resourceManager.formatWithPolkadotVaultLabel(R.string.account_details_parity_signer_alert, pokadotVaultVariant) + ) +} + +fun AccountInChain.polkadotVaultTitle(resourceManager: ResourceManager, metaAccount: MetaAccount): String { + val polkadotVaultVariant = metaAccount.type.asPolkadotVaultVariantOrThrow() + return resourceManager.formatWithPolkadotVaultLabel(R.string.account_details_parity_signer_not_supported, polkadotVaultVariant) +} + +fun AccountInChain.From.mapToAccountHeader(resourceManager: ResourceManager): TextHeader { + val resId = when (this) { + AccountInChain.From.META_ACCOUNT -> R.string.account_shared_secret + AccountInChain.From.CHAIN_ACCOUNT -> R.string.account_custom_secret + } + + return TextHeader(resourceManager.getString(resId)) +} + +fun notHasAccountComparator(): Comparator { + return compareBy { !it.hasChainAccount } +} + +fun hasAccountComparator(): Comparator { + return compareBy { it.hasChainAccount } +} + +fun Comparator.withChainComparator(): Comparator { + return then(Chain.defaultComparatorFrom(AccountInChain::chain)) +} + +val AccountInChain.hasChainAccount + get() = projection != null diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/model/AccountTypeAlert.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/model/AccountTypeAlert.kt index 988879a8a1..78360ebfcb 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/model/AccountTypeAlert.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/model/AccountTypeAlert.kt @@ -4,5 +4,6 @@ import io.novafoundation.nova.common.view.AlertView class AccountTypeAlert( val style: AlertView.Style, - val text: String + val message: String, + val subMessage: CharSequence? = null ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/currentStakeTargets/CurrentStakeTargetsFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/currentStakeTargets/CurrentStakeTargetsFragment.kt index 27302473b5..478105c208 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/currentStakeTargets/CurrentStakeTargetsFragment.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/currentStakeTargets/CurrentStakeTargetsFragment.kt @@ -62,7 +62,7 @@ abstract class CurrentStakeTargetsFragment : B viewModel.warningFlow.observe { if (it != null) { currentValidatorsOversubscribedMessage.makeVisible() - currentValidatorsOversubscribedMessage.setText(it) + currentValidatorsOversubscribedMessage.setMessage(it) } else { currentValidatorsOversubscribedMessage.makeGone() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/details/ValidatorDetailsFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/details/ValidatorDetailsFragment.kt index e599320b7e..36cbc37777 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/details/ValidatorDetailsFragment.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/details/ValidatorDetailsFragment.kt @@ -131,7 +131,7 @@ class ValidatorDetailsFragment : BaseFragment() { return AlertView(requireContext()).also { alertView -> alertView.setStylePreset(style) - alertView.setText(alert.descriptionRes) + alertView.setMessage(alert.descriptionRes) alertView.layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).also { params -> params.updateMarginsRelative(start = 16.dp, end = 16.dp, top = 12.dp) diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/SwapConfirmationFragment.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/SwapConfirmationFragment.kt index aa4db12ade..634b63d2b7 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/SwapConfirmationFragment.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/SwapConfirmationFragment.kt @@ -11,7 +11,7 @@ import io.novafoundation.nova.common.mixin.impl.observeValidations import io.novafoundation.nova.common.utils.applyStatusBarInsets import io.novafoundation.nova.common.view.bottomSheet.description.observeDescription import io.novafoundation.nova.common.view.setProgress -import io.novafoundation.nova.common.view.setTextOrHide +import io.novafoundation.nova.common.view.setMessageOrHide import io.novafoundation.nova.common.view.showValueOrHide import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.showWallet import io.novafoundation.nova.feature_account_api.presenatation.actions.setupExternalActions @@ -86,7 +86,7 @@ class SwapConfirmationFragment : BaseFragment() { viewModel.wallet.observe { swapConfirmationWallet.showWallet(it) } viewModel.addressFlow.observe { swapConfirmationAccount.showAddress(it) } - viewModel.slippageAlertMessage.observe { swapConfirmationAlert.setTextOrHide(it) } + viewModel.slippageAlertMessage.observe { swapConfirmationAlert.setMessageOrHide(it) } viewModel.validationProgress.observe(swapConfirmationButton::setProgress) } diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/options/SwapOptionsFragment.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/options/SwapOptionsFragment.kt index c44fe4bf86..052e68b87b 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/options/SwapOptionsFragment.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/options/SwapOptionsFragment.kt @@ -12,7 +12,7 @@ import io.novafoundation.nova.common.utils.bindTo import io.novafoundation.nova.common.validation.observeErrors import io.novafoundation.nova.common.view.bottomSheet.description.observeDescription import io.novafoundation.nova.common.view.setState -import io.novafoundation.nova.common.view.setTextOrHide +import io.novafoundation.nova.common.view.setMessageOrHide import io.novafoundation.nova.feature_swap_api.di.SwapFeatureApi import io.novafoundation.nova.feature_swap_impl.R import io.novafoundation.nova.feature_swap_impl.di.SwapFeatureComponent @@ -62,7 +62,7 @@ class SwapOptionsFragment : BaseFragment() { } viewModel.buttonState.observe { swapOptionsApplyButton.setState(it) } swapOptionsSlippageInput.observeErrors(viewModel.slippageInputValidationResult, viewModel.viewModelScope) - viewModel.slippageWarningState.observe { swapOptionsAlert.setTextOrHide(it) } + viewModel.slippageWarningState.observe { swapOptionsAlert.setMessageOrHide(it) } viewModel.resetButtonEnabled.observe { swapOptionsToolbar.setRightActionEnabled(it) } } } diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/sessions/approve/WalletConnectApproveSessionFragment.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/sessions/approve/WalletConnectApproveSessionFragment.kt index c93db0c4fd..d6fc409dd0 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/sessions/approve/WalletConnectApproveSessionFragment.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/presentation/sessions/approve/WalletConnectApproveSessionFragment.kt @@ -8,7 +8,7 @@ import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.utils.applyStatusBarInsets import io.novafoundation.nova.common.view.setState -import io.novafoundation.nova.common.view.setTextOrHide +import io.novafoundation.nova.common.view.setMessageOrHide import io.novafoundation.nova.common.view.showValueOrHide import io.novafoundation.nova.feature_account_api.presenatation.chain.showChainsOverview import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.setupSelectWalletMixin @@ -78,8 +78,8 @@ class WalletConnectApproveSessionFragment : BaseFragment - wcApproveSessionChainsAlert.setTextOrHide(sessionAlerts.unsupportedChains?.alertContent) - wcApproveSessionAccountsAlert.setTextOrHide(sessionAlerts.missingAccounts?.alertContent) + wcApproveSessionChainsAlert.setMessageOrHide(sessionAlerts.unsupportedChains?.alertContent) + wcApproveSessionAccountsAlert.setMessageOrHide(sessionAlerts.missingAccounts?.alertContent) } viewModel.allowButtonState.observe(wcApproveSessionAllow::setState) From 8fb5ea5525da6ec4b5a60b38399931865acf8ad3 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 19 Dec 2023 03:44:00 +0100 Subject: [PATCH 045/100] Clean code --- .../nova/app/root/navigation/Navigator.kt | 4 ++-- .../root/navigation/governance/GovernanceNavigator.kt | 4 ++-- .../app/root/navigation/settings/SettingsNavigator.kt | 4 ++-- .../navigation/staking/StartMultiStakingNavigator.kt | 2 +- .../staking/parachain/ParachainStakingNavigator.kt | 2 +- .../staking/relaychain/RelayStakingNavigator.kt | 2 +- app/src/main/res/navigation/main_nav_graph.xml | 10 +++++----- .../feature_account_impl/presentation/AccountRouter.kt | 2 +- .../account/details/WalletDetailsFragment.kt | 2 +- .../account/management/WalletManagmentViewModel.kt | 2 +- ...account_details.xml => fragment_wallet_details.xml} | 0 .../presentation/CrowdloanRouter.kt | 2 +- .../presentation/main/CrowdloanViewModel.kt | 2 +- .../presentation/GovernanceRouter.kt | 2 +- .../delegate/detail/main/DelegateDetailsViewModel.kt | 2 +- .../referenda/details/ReferendumDetailsViewModel.kt | 2 +- .../nova/feature_settings_impl/SettingsRouter.kt | 2 +- .../presentation/settings/SettingsViewModel.kt | 2 +- .../feature_staking_impl/presentation/StakingRouter.kt | 2 +- 19 files changed, 25 insertions(+), 25 deletions(-) rename feature-account-impl/src/main/res/layout/{fragment_account_details.xml => fragment_wallet_details.xml} (100%) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt index f2be521fa2..ee52154cbb 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/Navigator.kt @@ -179,7 +179,7 @@ class Navigator( val destination = when (val currentDestinationId = navController?.currentDestination?.id) { R.id.welcomeFragment -> R.id.action_welcomeFragment_to_mnemonic_nav_graph R.id.createAccountFragment -> R.id.action_createAccountFragment_to_mnemonic_nav_graph - R.id.accountDetailsFragment -> R.id.action_accountDetailsFragment_to_mnemonic_nav_graph + R.id.walletDetailsFragment -> R.id.action_accountDetailsFragment_to_mnemonic_nav_graph else -> throw IllegalArgumentException("Unknown current destination to open mnemonic screen: $currentDestinationId") } @@ -409,7 +409,7 @@ class Navigator( } } - override fun openAccountDetails(metaId: Long) { + override fun openWalletDetails(metaId: Long) { val extras = WalletDetailsFragment.getBundle(metaId) navController?.navigate(R.id.action_open_account_details, extras) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt index 16db060824..ef4fa33e2f 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/governance/GovernanceNavigator.kt @@ -79,8 +79,8 @@ class GovernanceNavigator( } } - override fun openAccountDetails(id: Long) { - commonNavigator.openAccountDetails(id) + override fun openWalletDetails(id: Long) { + commonNavigator.openWalletDetails(id) } override fun openAddDelegation() { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt index 541e747b7a..445fd90580 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/settings/SettingsNavigator.kt @@ -30,8 +30,8 @@ class SettingsNavigator( args = PincodeFragment.getPinCodeBundle(PinCodeAction.Change) ) - override fun openAccountDetails(metaId: Long) { - delegate.openAccountDetails(metaId) + override fun openWalletDetails(metaId: Long) { + delegate.openWalletDetails(metaId) } override fun openSwitchWallet() { diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StartMultiStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StartMultiStakingNavigator.kt index 0b99a5d81f..8ebc2c4a98 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StartMultiStakingNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/StartMultiStakingNavigator.kt @@ -58,6 +58,6 @@ class StartMultiStakingNavigator( } override fun goToWalletDetails(metaId: Long) { - commonNavigationHolder.openAccountDetails(metaId) + commonNavigationHolder.openWalletDetails(metaId) } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/ParachainStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/ParachainStakingNavigator.kt index 160034f372..cf99622a45 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/ParachainStakingNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/parachain/ParachainStakingNavigator.kt @@ -41,7 +41,7 @@ class ParachainStakingNavigator( ) override fun openWalletDetails(metaId: Long) { - commonNavigator.openAccountDetails(metaId) + commonNavigator.openWalletDetails(metaId) } override fun returnToStakingMain() = performNavigation(R.id.back_to_staking_main) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt index 7f426bd48c..2c797cef2e 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/staking/relaychain/RelayStakingNavigator.kt @@ -48,7 +48,7 @@ class RelayStakingNavigator( override fun openSwitchWallet() = commonNavigator.openSwitchWallet() - override fun openAccountDetails(metaAccountId: Long) = commonNavigator.openAccountDetails(metaAccountId) + override fun openWalletDetails(metaAccountId: Long) = commonNavigator.openWalletDetails(metaAccountId) override fun openCustomRebond() { performNavigation(R.id.action_stakingFragment_to_customRebondFragment) diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml index c3674fdc45..41cab41e5b 100644 --- a/app/src/main/res/navigation/main_nav_graph.xml +++ b/app/src/main/res/navigation/main_nav_graph.xml @@ -85,7 +85,7 @@ app:exitAnim="@anim/fragment_close_exit" app:popEnterAnim="@anim/fragment_open_enter" app:popExitAnim="@anim/fragment_open_exit" - app:popUpTo="@+id/accountDetailsFragment" /> + app:popUpTo="@+id/walletDetailsFragment" /> + android:label="WalletDetailsFragment" + tools:layout="@layout/fragment_wallet_details"> (), ChainAccou inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ) = layoutInflater.inflate(R.layout.fragment_account_details, container, false) + ) = layoutInflater.inflate(R.layout.fragment_wallet_details, container, false) override fun initViews() { accountDetailsToolbar.setHomeButtonListener { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt index 52854995d3..c4f674abb6 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentViewModel.kt @@ -40,7 +40,7 @@ class WalletManagmentViewModel( val confirmAccountDeletion = actionAwaitableMixinFactory.confirmingOrDenyingAction() fun accountClicked(accountModel: AccountUi) { - accountRouter.openAccountDetails(accountModel.id) + accountRouter.openWalletDetails(accountModel.id) } fun editClicked() { diff --git a/feature-account-impl/src/main/res/layout/fragment_account_details.xml b/feature-account-impl/src/main/res/layout/fragment_wallet_details.xml similarity index 100% rename from feature-account-impl/src/main/res/layout/fragment_account_details.xml rename to feature-account-impl/src/main/res/layout/fragment_wallet_details.xml diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/CrowdloanRouter.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/CrowdloanRouter.kt index c7c173aecf..92ba6c56c6 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/CrowdloanRouter.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/CrowdloanRouter.kt @@ -32,5 +32,5 @@ interface CrowdloanRouter { fun openSwitchWallet() - fun openAccountDetails(metaId: Long) + fun openWalletDetails(metaId: Long) } diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/main/CrowdloanViewModel.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/main/CrowdloanViewModel.kt index b952165a16..8b5126fdbf 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/main/CrowdloanViewModel.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/main/CrowdloanViewModel.kt @@ -201,7 +201,7 @@ class CrowdloanViewModel( is MainCrowdloanValidationFailure.NoRelaychainAccount -> handleChainAccountNotFound( failure = failure, resourceManager = resourceManager, - goToWalletDetails = { router.openAccountDetails(failure.account.id) }, + goToWalletDetails = { router.openWalletDetails(failure.account.id) }, addAccountDescriptionRes = R.string.crowdloan_missing_account_message ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/GovernanceRouter.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/GovernanceRouter.kt index bfc13acf45..5def937a83 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/GovernanceRouter.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/GovernanceRouter.kt @@ -45,7 +45,7 @@ interface GovernanceRouter : ReturnableRouter { fun finishUnlockFlow(shouldCloseLocksScreen: Boolean) - fun openAccountDetails(id: Long) + fun openWalletDetails(id: Long) fun openAddDelegation() diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegate/detail/main/DelegateDetailsViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegate/detail/main/DelegateDetailsViewModel.kt index 6cef15552e..7eddf9c460 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegate/detail/main/DelegateDetailsViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegate/detail/main/DelegateDetailsViewModel.kt @@ -270,7 +270,7 @@ class DelegateDetailsViewModel( is AddDelegationValidationFailure.NoChainAccountFailure -> handleChainAccountNotFound( failure = failure, resourceManager = resourceManager, - goToWalletDetails = { router.openAccountDetails(failure.account.id) }, + goToWalletDetails = { router.openWalletDetails(failure.account.id) }, addAccountDescriptionRes = R.string.add_delegation_missing_account_message ) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsViewModel.kt index ffcaa3e641..efbc8b0148 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/details/ReferendumDetailsViewModel.kt @@ -443,7 +443,7 @@ class ReferendumDetailsViewModel( is ReferendumPreVoteValidationFailure.NoRelaychainAccount -> handleChainAccountNotFound( failure = failure, resourceManager = resourceManager, - goToWalletDetails = { router.openAccountDetails(failure.account.id) }, + goToWalletDetails = { router.openWalletDetails(failure.account.id) }, addAccountDescriptionRes = R.string.referendum_missing_account_message ) } diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/SettingsRouter.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/SettingsRouter.kt index 1783cc5a17..992c696f96 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/SettingsRouter.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/SettingsRouter.kt @@ -10,7 +10,7 @@ interface SettingsRouter { fun openChangePinCode() - fun openAccountDetails(metaId: Long) + fun openWalletDetails(metaId: Long) fun openSwitchWallet() diff --git a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsViewModel.kt b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsViewModel.kt index 91200bea58..37561785af 100644 --- a/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsViewModel.kt +++ b/feature-settings-impl/src/main/java/io/novafoundation/nova/feature_settings_impl/presentation/settings/SettingsViewModel.kt @@ -206,7 +206,7 @@ class SettingsViewModel( fun accountActionsClicked() = launch { val selectedWalletId = selectedAccountUseCase.getSelectedMetaAccount().id - router.openAccountDetails(selectedWalletId) + router.openWalletDetails(selectedWalletId) } fun selectedWalletClicked() { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/StakingRouter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/StakingRouter.kt index 36c001abcb..7682f0bc93 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/StakingRouter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/StakingRouter.kt @@ -74,7 +74,7 @@ interface StakingRouter { fun openConfirmRewardDestination(payload: ConfirmRewardDestinationPayload) - fun openAccountDetails(metaAccountId: Long) + fun openWalletDetails(metaAccountId: Long) fun openRebag() From 40156d39fcab667037c9573a82f580cdbeccaba9 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 19 Dec 2023 03:48:24 +0100 Subject: [PATCH 046/100] Fixed wildcard import --- .../account/details/mixin/common/AccountFormatter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/AccountFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/AccountFormatter.kt index 49bb99ba9c..9e4b3bab78 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/AccountFormatter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/common/AccountFormatter.kt @@ -4,7 +4,7 @@ import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.filterToSet import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi -import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.* +import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi From cbc93f53ae30400524407e06bb0867998e6474ca Mon Sep 17 00:00:00 2001 From: Valentun Date: Tue, 19 Dec 2023 18:20:38 +0300 Subject: [PATCH 047/100] Realtime proxy updates --- .../network/runtime/binding/GenericCall.kt | 7 + .../nova/common/utils/FearlessLibExt.kt | 11 +- .../SubstrateRealtimeOperationFetcher.kt | 4 +- .../equilibrium/EquilibriumAssetHistory.kt | 8 +- .../assets/history/orml/OrmlAssetHistory.kt | 8 +- .../substrate/AssetConversionSwapExtractor.kt | 50 ++- .../history/realtime/substrate/Common.kt | 10 +- .../RealSubstrateRealtimeOperationFetcher.kt | 39 +-- .../statemine/StatemineAssetHistory.kt | 8 +- .../history/utility/NativeAssetHistory.kt | 8 +- .../nova/runtime/di/RuntimeApi.kt | 3 + .../nova/runtime/di/RuntimeModule.kt | 8 + .../extrinsic/visitor/api/ExtrinsicWalk.kt | 50 +++ .../extrinsic/visitor/api/ExtrinsicWalkExt.kt | 12 + .../visitor/impl/ExtrinsicVisitorLogger.kt | 26 ++ .../visitor/impl/MutableEventQueue.kt | 132 +++++++ .../extrinsic/visitor/impl/NestedCallNode.kt | 56 +++ .../visitor/impl/RealExtrinsicWalk.kt | 139 ++++++++ .../visitor/impl/nodes/proxy/ProxyNode.kt | 105 ++++++ .../runtime/repository/ExtrinsicWithEvents.kt | 38 +- .../extrinsic/visitor/impl/ProxyWalkTest.kt | 330 ++++++++++++++++++ 21 files changed, 973 insertions(+), 79 deletions(-) create mode 100644 common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/GenericCall.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalk.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalkExt.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/MutableEventQueue.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/NestedCallNode.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt create mode 100644 runtime/src/test/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ProxyWalkTest.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/GenericCall.kt b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/GenericCall.kt new file mode 100644 index 0000000000..bfdb14ce9f --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/GenericCall.kt @@ -0,0 +1,7 @@ +package io.novafoundation.nova.common.data.network.runtime.binding + +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +fun bindGenericCall(decoded: Any?) : GenericCall.Instance { + return decoded.cast() +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index bcc9cf7d0b..4bba2b743e 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -25,9 +25,11 @@ import jp.co.soramitsu.fearless_utils.runtime.definitions.types.bytesOrNull import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.DefaultSignedExtensions import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericEvent import jp.co.soramitsu.fearless_utils.runtime.definitions.types.skipAliases +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.genesisHash import jp.co.soramitsu.fearless_utils.runtime.metadata.RuntimeMetadata @@ -35,6 +37,7 @@ import jp.co.soramitsu.fearless_utils.runtime.metadata.callOrNull import jp.co.soramitsu.fearless_utils.runtime.metadata.fullName import jp.co.soramitsu.fearless_utils.runtime.metadata.module import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Constant +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Event import jp.co.soramitsu.fearless_utils.runtime.metadata.module.FunctionArgument import jp.co.soramitsu.fearless_utils.runtime.metadata.module.MetadataFunction import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Module @@ -49,13 +52,11 @@ import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.addressPrefix import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAccountId import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAddress +import org.web3j.crypto.Sign import java.io.ByteArrayOutputStream import java.math.BigInteger import java.nio.ByteBuffer import java.nio.ByteOrder -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic.EncodingInstance.CallRepresentation -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw -import org.web3j.crypto.Sign typealias PalletName = String @@ -231,6 +232,8 @@ fun RuntimeMetadata.assetConversionOrNull() = moduleOrNull(Modules.ASSET_CONVERS fun RuntimeMetadata.assetConversion() = module(Modules.ASSET_CONVERSION) +fun RuntimeMetadata.proxy() = module(Modules.PROXY) + fun RuntimeMetadata.firstExistingModuleName(vararg options: String): String { return options.first(::hasModule) } @@ -280,6 +283,8 @@ fun GenericCall.Instance.instanceOf(moduleName: String, vararg callNames: String fun GenericEvent.Instance.instanceOf(moduleName: String, eventName: String): Boolean = moduleName == module.name && eventName == event.name +fun GenericEvent.Instance.instanceOf(event: Event): Boolean = event.index == this.event.index + fun structOf(vararg pairs: Pair) = Struct.Instance(mapOf(*pairs)) fun SignedRaw.toEcdsaSignatureData(): Sign.SignatureData { diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/history/realtime/substrate/SubstrateRealtimeOperationFetcher.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/history/realtime/substrate/SubstrateRealtimeOperationFetcher.kt index 0c353d9f4b..5d767e3dc0 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/history/realtime/substrate/SubstrateRealtimeOperationFetcher.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/history/realtime/substrate/SubstrateRealtimeOperationFetcher.kt @@ -3,8 +3,8 @@ package io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets. import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.history.realtime.RealtimeHistoryUpdate import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.history.realtime.substrate.SubstrateRealtimeOperationFetcher.Extractor import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.history.realtime.substrate.SubstrateRealtimeOperationFetcher.Factory +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents interface SubstrateRealtimeOperationFetcher { @@ -17,7 +17,7 @@ interface SubstrateRealtimeOperationFetcher { interface Extractor { suspend fun extractRealtimeHistoryUpdates( - extrinsic: ExtrinsicWithEvents, + extrinsicVisit: ExtrinsicVisit, chain: Chain, chainAsset: Chain.Asset ): RealtimeHistoryUpdate.Type? diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/equilibrium/EquilibriumAssetHistory.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/equilibrium/EquilibriumAssetHistory.kt index 5f8b5df7e2..6a6f05a175 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/equilibrium/EquilibriumAssetHistory.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/equilibrium/EquilibriumAssetHistory.kt @@ -14,10 +14,10 @@ import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets import io.novafoundation.nova.feature_wallet_impl.data.network.subquery.SubQueryOperationsApi import io.novafoundation.nova.feature_wallet_impl.data.storage.TransferCursorStorage import io.novafoundation.nova.runtime.ext.isUtilityAsset +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit 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.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall import jp.co.soramitsu.fearless_utils.runtime.metadata.call @@ -44,19 +44,19 @@ class EquilibriumAssetHistory( private inner class TransferExtractor : SubstrateRealtimeOperationFetcher.Extractor { override suspend fun extractRealtimeHistoryUpdates( - extrinsic: ExtrinsicWithEvents, + extrinsicVisit: ExtrinsicVisit, chain: Chain, chainAsset: Chain.Asset ): RealtimeHistoryUpdate.Type? { val runtime = chainRegistry.getRuntime(chain.id) - val call = extrinsic.extrinsic.call + val call = extrinsicVisit.call if (!call.isTransfer(runtime)) return null val amount = bindNumber(call.arguments["value"]) return RealtimeHistoryUpdate.Type.Transfer( - senderId = bindAccountIdentifier(extrinsic.extrinsic.signature!!.accountIdentifier), + senderId = extrinsicVisit.origin, recipientId = bindAccountIdentifier(call.arguments["to"]), amountInPlanks = amount, chainAsset = chainAsset, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/orml/OrmlAssetHistory.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/orml/OrmlAssetHistory.kt index 09ac4e69a4..c3675ecbea 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/orml/OrmlAssetHistory.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/orml/OrmlAssetHistory.kt @@ -16,10 +16,10 @@ import io.novafoundation.nova.feature_wallet_impl.data.storage.TransferCursorSto import io.novafoundation.nova.runtime.ext.findAssetByOrmlCurrencyId import io.novafoundation.nova.runtime.ext.isSwapSupported import io.novafoundation.nova.runtime.ext.isUtilityAsset +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit 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.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall import jp.co.soramitsu.fearless_utils.runtime.metadata.call @@ -48,20 +48,20 @@ class OrmlAssetHistory( private inner class TransferExtractor : SubstrateRealtimeOperationFetcher.Extractor { override suspend fun extractRealtimeHistoryUpdates( - extrinsic: ExtrinsicWithEvents, + extrinsicVisit: ExtrinsicVisit, chain: Chain, chainAsset: Chain.Asset ): RealtimeHistoryUpdate.Type? { val runtime = chainRegistry.getRuntime(chain.id) - val call = extrinsic.extrinsic.call + val call = extrinsicVisit.call if (!call.isTransfer(runtime)) return null val inferredAsset = chain.findAssetByOrmlCurrencyId(runtime, call.arguments["currency_id"]) ?: return null val amount = bindNumber(call.arguments["amount"]) return RealtimeHistoryUpdate.Type.Transfer( - senderId = bindAccountIdentifier(extrinsic.extrinsic.signature!!.accountIdentifier), + senderId = extrinsicVisit.origin, recipientId = bindAccountIdentifier(call.arguments["dest"]), amountInPlanks = amount, chainAsset = inferredAsset, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/AssetConversionSwapExtractor.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/AssetConversionSwapExtractor.kt index a45de8848c..b58d8d69f2 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/AssetConversionSwapExtractor.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/AssetConversionSwapExtractor.kt @@ -1,7 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.history.realtime.substrate import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountId -import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountIdentifier import io.novafoundation.nova.common.data.network.runtime.binding.bindList import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber import io.novafoundation.nova.common.utils.Modules @@ -10,16 +9,13 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.h import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.model.ChainAssetWithAmount import io.novafoundation.nova.runtime.ext.commissionAsset +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.multiLocation.bindMultiLocation import io.novafoundation.nova.runtime.multiNetwork.multiLocation.converter.MultiLocationConverter import io.novafoundation.nova.runtime.multiNetwork.multiLocation.converter.MultiLocationConverterFactory -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicStatus -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.findAllEvents -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.hasEvent -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.nativeFee -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.status +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.findAllOfType +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.requireNativeFee import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.coroutineContext @@ -31,11 +27,11 @@ class AssetConversionSwapExtractor( private val calls = listOf("swap_exact_tokens_for_tokens", "swap_tokens_for_exact_tokens") override suspend fun extractRealtimeHistoryUpdates( - extrinsic: ExtrinsicWithEvents, + extrinsicVisit: ExtrinsicVisit, chain: Chain, chainAsset: Chain.Asset ): RealtimeHistoryUpdate.Type? { - val call = extrinsic.extrinsic.call + val call = extrinsicVisit.call val callArgs = call.arguments if (!call.isSwap()) return null @@ -47,29 +43,28 @@ class AssetConversionSwapExtractor( val assetIn = multiLocationConverter.toChainAsset(path.first()) ?: return null val assetOut = multiLocationConverter.toChainAsset(path.last()) ?: return null - val (amountIn, amountOut) = extrinsic.extractSwapAmounts() + val (amountIn, amountOut) = extrinsicVisit.extractSwapAmounts() - val who = bindAccountIdentifier(extrinsic.extrinsic.signature!!.accountIdentifier) val sendTo = bindAccountId(callArgs["send_to"]) - val fee = extrinsic.extractFee(chain, multiLocationConverter) + val fee = extrinsicVisit.extractFee(chain, multiLocationConverter) return RealtimeHistoryUpdate.Type.Swap( amountIn = ChainAssetWithAmount(assetIn, amountIn), amountOut = ChainAssetWithAmount(assetOut, amountOut), amountFee = fee, - senderId = who, + senderId = extrinsicVisit.origin, receiverId = sendTo ) } - private fun ExtrinsicWithEvents.extractSwapAmounts(): Pair { - val isCustomFeeTokenUsed = hasEvent(Modules.ASSET_TX_PAYMENT, "AssetTxFeePaid") - val extrinsicStatus = status() - val allSwaps = findAllEvents(Modules.ASSET_CONVERSION, "SwapExecuted") + private fun ExtrinsicVisit.extractSwapAmounts(): Pair { + // We check for custom fee usage from root extrinsic since `extrinsicVisit` will cut it out when nested calls are present + val isCustomFeeTokenUsed = rootExtrinsic.events.assetTxFeePaidEvent() != null + val allSwaps = events.findAllOfType(Modules.ASSET_CONVERSION, "SwapExecuted") val swapExecutedEvent = when { - extrinsicStatus == ExtrinsicStatus.FAILURE -> null // we wont be able to extract swap from event + !success -> null // we wont be able to extract swap from event isCustomFeeTokenUsed -> { // Swaps with custom fee token produce up to free SwapExecuted events, in the following order: @@ -96,16 +91,16 @@ class AssetConversionSwapExtractor( } // failed swap, extract from call args - extrinsic.call.function.name == "swap_exact_tokens_for_tokens" -> { - val amountIn = bindNumber(extrinsic.call.arguments["amount_in"]) - val amountOutMin = bindNumber(extrinsic.call.arguments["amount_out_min"]) + call.function.name == "swap_exact_tokens_for_tokens" -> { + val amountIn = bindNumber(call.arguments["amount_in"]) + val amountOutMin = bindNumber(call.arguments["amount_out_min"]) amountIn to amountOutMin } - extrinsic.call.function.name == "swap_tokens_for_exact_tokens" -> { - val amountOut = bindNumber(extrinsic.call.arguments["amount_out"]) - val amountInMax = bindNumber(extrinsic.call.arguments["amount_in_max"]) + call.function.name == "swap_tokens_for_exact_tokens" -> { + val amountOut = bindNumber(call.arguments["amount_out"]) + val amountInMax = bindNumber(call.arguments["amount_in_max"]) amountInMax to amountOut } @@ -114,14 +109,15 @@ class AssetConversionSwapExtractor( } } - private suspend fun ExtrinsicWithEvents.extractFee( + private suspend fun ExtrinsicVisit.extractFee( chain: Chain, multiLocationConverter: MultiLocationConverter ): ChainAssetWithAmount { - val assetFee = assetFee(multiLocationConverter) + // We check for fee usage from root extrinsic since `extrinsicVisit` will cut it out when nested calls are present + val assetFee = rootExtrinsic.events.assetFee(multiLocationConverter) if (assetFee != null) return assetFee - val nativeFee = nativeFee()!! + val nativeFee = rootExtrinsic.events.requireNativeFee() return ChainAssetWithAmount(chain.commissionAsset, nativeFee) } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/Common.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/Common.kt index 7ed5ad9f1d..41f3891960 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/Common.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/Common.kt @@ -5,14 +5,18 @@ import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.feature_wallet_api.domain.model.ChainAssetWithAmount import io.novafoundation.nova.runtime.multiNetwork.multiLocation.bindMultiLocation import io.novafoundation.nova.runtime.multiNetwork.multiLocation.converter.MultiLocationConverter -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.findEvent +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericEvent -suspend fun ExtrinsicWithEvents.assetFee(multiLocationConverter: MultiLocationConverter): ChainAssetWithAmount? { - val event = findEvent(Modules.ASSET_TX_PAYMENT, "AssetTxFeePaid") ?: return null +suspend fun List.assetFee(multiLocationConverter: MultiLocationConverter): ChainAssetWithAmount? { + val event = assetTxFeePaidEvent() ?: return null val (_, actualFee, tip, assetId) = event.arguments val totalFee = bindNumber(actualFee) + bindNumber(tip) val chainAsset = multiLocationConverter.toChainAsset(bindMultiLocation(assetId)) ?: return null return ChainAssetWithAmount(chainAsset, totalFee) } + +fun List.assetTxFeePaidEvent(): GenericEvent.Instance? { + return findEvent(Modules.ASSET_TX_PAYMENT, "AssetTxFeePaid") +} diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/RealSubstrateRealtimeOperationFetcher.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/RealSubstrateRealtimeOperationFetcher.kt index af4c67e3c2..2d57cdd6ac 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/RealSubstrateRealtimeOperationFetcher.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/RealSubstrateRealtimeOperationFetcher.kt @@ -5,22 +5,22 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.h import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.history.realtime.substrate.SubstrateRealtimeOperationFetcher.Extractor import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.history.realtime.substrate.SubstrateRealtimeOperationFetcher.Factory import io.novafoundation.nova.feature_wallet_api.domain.model.Operation +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicWalk +import io.novafoundation.nova.runtime.extrinsic.visitor.api.walkToList import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.multiLocation.converter.MultiLocationConverterFactory import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.EventsRepository -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicStatus -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.status internal class SubstrateRealtimeOperationFetcherFactory( private val multiLocationConverterFactory: MultiLocationConverterFactory, - private val eventsRepository: EventsRepository + private val eventsRepository: EventsRepository, + private val callWalk: ExtrinsicWalk, ) : Factory { override fun create(sources: List): SubstrateRealtimeOperationFetcher { val extractors = sources.map { it.extractor() } - return RealSubstrateRealtimeOperationFetcher(eventsRepository, extractors) + return RealSubstrateRealtimeOperationFetcher(eventsRepository, extractors, callWalk) } private fun Factory.Source.extractor(): Extractor { @@ -43,7 +43,8 @@ internal class SubstrateRealtimeOperationFetcherFactory( private class RealSubstrateRealtimeOperationFetcher( private val repository: EventsRepository, - private val extractors: List + private val extractors: List, + private val callWalk: ExtrinsicWalk, ) : SubstrateRealtimeOperationFetcher { override suspend fun extractRealtimeHistoryUpdates( @@ -54,23 +55,19 @@ private class RealSubstrateRealtimeOperationFetcher( val extrinsics = repository.getExtrinsicsWithEvents(chain.id, blockHash) return extrinsics.flatMap { extrinsic -> - extractors.mapNotNull { - val type = runCatching { it.extractRealtimeHistoryUpdates(extrinsic, chain, chainAsset) }.getOrNull() ?: return@mapNotNull null + val visits = runCatching { callWalk.walkToList(extrinsic, chain.id) }.getOrElse { emptyList() } - RealtimeHistoryUpdate( - txHash = extrinsic.extrinsicHash, - status = extrinsic.operationStatus(), - type = type - ) - } - } - } + visits.flatMap { extrinsicVisit -> + extractors.mapNotNull { + val type = runCatching { it.extractRealtimeHistoryUpdates(extrinsicVisit, chain, chainAsset) }.getOrNull() ?: return@mapNotNull null - private fun ExtrinsicWithEvents.operationStatus(): Operation.Status { - return when (status()) { - ExtrinsicStatus.SUCCESS -> Operation.Status.COMPLETED - ExtrinsicStatus.FAILURE -> Operation.Status.FAILED - null -> Operation.Status.PENDING + RealtimeHistoryUpdate( + txHash = extrinsic.extrinsicHash, + status = Operation.Status.fromSuccess(extrinsicVisit.success), + type = type + ) + } + } } } } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/statemine/StatemineAssetHistory.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/statemine/StatemineAssetHistory.kt index 090a81de2f..513d05d38e 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/statemine/StatemineAssetHistory.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/statemine/StatemineAssetHistory.kt @@ -16,11 +16,11 @@ import io.novafoundation.nova.runtime.ext.isSwapSupported import io.novafoundation.nova.runtime.ext.isUtilityAsset import io.novafoundation.nova.runtime.ext.palletNameOrDefault import io.novafoundation.nova.runtime.ext.requireStatemine +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.hasSameId import io.novafoundation.nova.runtime.multiNetwork.getRuntime -import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall import jp.co.soramitsu.fearless_utils.runtime.metadata.call @@ -52,19 +52,19 @@ class StatemineAssetHistory( private inner class TransferExtractor : SubstrateRealtimeOperationFetcher.Extractor { override suspend fun extractRealtimeHistoryUpdates( - extrinsic: ExtrinsicWithEvents, + extrinsicVisit: ExtrinsicVisit, chain: Chain, chainAsset: Chain.Asset ): RealtimeHistoryUpdate.Type? { val runtime = chainRegistry.getRuntime(chain.id) - val call = extrinsic.extrinsic.call + val call = extrinsicVisit.call if (!call.isTransfer(runtime, chainAsset)) return null val amount = bindNumber(call.arguments["amount"]) return RealtimeHistoryUpdate.Type.Transfer( - senderId = bindAccountIdentifier(extrinsic.extrinsic.signature!!.accountIdentifier), + senderId = extrinsicVisit.origin, recipientId = bindAccountIdentifier(call.arguments["target"]), amountInPlanks = amount, chainAsset = chainAsset, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/utility/NativeAssetHistory.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/utility/NativeAssetHistory.kt index 9101dd50e0..7690743ce3 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/utility/NativeAssetHistory.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/utility/NativeAssetHistory.kt @@ -14,10 +14,10 @@ import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets import io.novafoundation.nova.feature_wallet_impl.data.network.subquery.SubQueryOperationsApi import io.novafoundation.nova.feature_wallet_impl.data.storage.TransferCursorStorage import io.novafoundation.nova.runtime.ext.isSwapSupported +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit 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.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall import jp.co.soramitsu.fearless_utils.runtime.metadata.callOrNull @@ -49,19 +49,19 @@ class NativeAssetHistory( private inner class TransferExtractor : SubstrateRealtimeOperationFetcher.Extractor { override suspend fun extractRealtimeHistoryUpdates( - extrinsic: ExtrinsicWithEvents, + extrinsicVisit: ExtrinsicVisit, chain: Chain, chainAsset: Chain.Asset ): RealtimeHistoryUpdate.Type? { val runtime = chainRegistry.getRuntime(chain.id) - val call = extrinsic.extrinsic.call + val call = extrinsicVisit.call if (!call.isTransfer(runtime)) return null val amount = bindNumber(call.arguments["value"]) return RealtimeHistoryUpdate.Type.Transfer( - senderId = bindAccountIdentifier(extrinsic.extrinsic.signature!!.accountIdentifier), + senderId = bindAccountIdentifier(extrinsicVisit.origin), recipientId = bindAccountIdentifier(call.arguments["dest"]), amountInPlanks = amount, chainAsset = chainAsset, diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/di/RuntimeApi.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/di/RuntimeApi.kt index bb67fd505d..7228c30b96 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/di/RuntimeApi.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/di/RuntimeApi.kt @@ -9,6 +9,7 @@ import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicValidityUseCase import io.novafoundation.nova.runtime.extrinsic.MortalityConstructor import io.novafoundation.nova.runtime.extrinsic.multi.ExtrinsicSplitter +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicWalk import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.ChainSyncService import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection @@ -83,4 +84,6 @@ interface RuntimeApi { val gasPriceProviderFactory: GasPriceProviderFactory val multiLocationConverterFactory: MultiLocationConverterFactory + + val extrinsicWalk: ExtrinsicWalk } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/di/RuntimeModule.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/di/RuntimeModule.kt index a21cc404fa..f5c66ce297 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/di/RuntimeModule.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/di/RuntimeModule.kt @@ -21,6 +21,8 @@ import io.novafoundation.nova.runtime.extrinsic.MortalityConstructor import io.novafoundation.nova.runtime.extrinsic.RealExtrinsicValidityUseCase import io.novafoundation.nova.runtime.extrinsic.multi.ExtrinsicSplitter import io.novafoundation.nova.runtime.extrinsic.multi.RealExtrinsicSplitter +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicWalk +import io.novafoundation.nova.runtime.extrinsic.visitor.impl.RealExtrinsicWalk import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.multiLocation.converter.MultiLocationConverterFactory import io.novafoundation.nova.runtime.multiNetwork.qr.MultiChainQrSharingFactory @@ -206,4 +208,10 @@ class RuntimeModule { fun provideMultiLocationConverterFactory(chainRegistry: ChainRegistry): MultiLocationConverterFactory { return MultiLocationConverterFactory(chainRegistry) } + + @Provides + @ApplicationScope + fun provideExtrinsicWalk( + chainRegistry: ChainRegistry, + ): ExtrinsicWalk = RealExtrinsicWalk(chainRegistry) } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalk.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalk.kt new file mode 100644 index 0000000000..3931d71eb0 --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalk.kt @@ -0,0 +1,50 @@ +package io.novafoundation.nova.runtime.extrinsic.visitor.api + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericEvent + +interface ExtrinsicWalk { + + suspend fun walk( + source: ExtrinsicWithEvents, + chainId: ChainId, + visitor: ExtrinsicVisitor + ) +} + +fun interface ExtrinsicVisitor { + + fun visit(visit: ExtrinsicVisit) +} + +data class ExtrinsicVisit( + + /** + * Whole extrinsic object. Useful for accessing data outside if the current visit scope, e.g. some top-level events + */ + val rootExtrinsic: ExtrinsicWithEvents, + + /** + * Call that is currently visiting + */ + val call: GenericCall.Instance, + + /** + * Whether call succeeded or not. + * Call is considered successful when it succeeds itself as well as its outer parents succeeds + */ + val success: Boolean, + + /** + * All events that are related to this specific call + */ + val events: List, + + /** + * Origin's account id that this call has been dispatched with + */ + val origin: AccountId +) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalkExt.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalkExt.kt new file mode 100644 index 0000000000..fbafac57fb --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalkExt.kt @@ -0,0 +1,12 @@ +package io.novafoundation.nova.runtime.extrinsic.visitor.api + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents + +suspend fun ExtrinsicWalk.walkToList(source: ExtrinsicWithEvents, chainId: ChainId): List { + return buildList { + walk(source, chainId) { visitedCall -> + add(visitedCall) + } + } +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt new file mode 100644 index 0000000000..a6fb78c889 --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt @@ -0,0 +1,26 @@ +package io.novafoundation.nova.runtime.extrinsic.visitor.impl + +import android.util.Log + +internal interface ExtrinsicVisitorLogger { + + fun info(message: String) + + fun error(message: String) +} + +internal class IndentVisitorLogger( + private val tag: String = "ExtrinsicVisitor", + private val indent: Int = 0 +) : ExtrinsicVisitorLogger { + + private val indentPrefix = " ".repeat(indent) + + override fun info(message: String) { + Log.d(tag, indentPrefix + message) + } + + override fun error(message: String) { + Log.e(tag, indentPrefix + message) + } +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/MutableEventQueue.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/MutableEventQueue.kt new file mode 100644 index 0000000000..64bc37144e --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/MutableEventQueue.kt @@ -0,0 +1,132 @@ +package io.novafoundation.nova.runtime.extrinsic.visitor.impl; + +import io.novafoundation.nova.common.utils.instanceOf +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericEvent +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Event + +internal interface MutableEventQueue : EventQueue { + + /** + * Removes last event matching one of eventTypes + */ + fun popFromEnd(vararg eventTypes: Event) + + /** + * Takes and removes all events that go after last event matching one of eventTypes. If no matched event found, + * all available events are returned + */ + fun takeTail(vararg eventTypes: Event): List + + /** + * Takes and removes all events that go after specified inclusive index + * @param endInclusive + */ + fun takeAllAfterInclusive(endInclusive: Int): List + + /** + * Takes and removes last event matching one of eventTypes + */ + fun takeFromEnd(vararg eventTypes: Event): GenericEvent.Instance? +} + +internal interface EventQueue { + + fun all(): List + + fun peekItemFromEnd(vararg eventTypes: Event, endExclusive: Int): EventWithIndex? + + fun indexOfLast(vararg eventTypes: Event, endExclusive: Int): Int? +} + +@Suppress("NOTHING_TO_INLINE") +internal inline fun EventQueue.peekItemFromEndOrThrow(vararg eventTypes: Event, endExclusive: Int): EventWithIndex { + return requireNotNull(peekItemFromEnd(*eventTypes, endExclusive = endExclusive)) { + "No required event found for types ${eventTypes.joinToString { it.name }}" + } +} + +@Suppress("NOTHING_TO_INLINE") +internal inline fun MutableEventQueue.takeFromEndOrThrow(vararg eventTypes: Event): GenericEvent.Instance { + return requireNotNull(takeFromEnd(*eventTypes)) { + "No required event found for types ${eventTypes.joinToString { it.name }}" + } +} + + +data class EventWithIndex(val event: GenericEvent.Instance, val eventIndex: Int) + +class RealEventQueue(event: List) : MutableEventQueue { + + private val events: MutableList = event.toMutableList() + + override fun all(): List { + return events + } + + override fun peekItemFromEnd(vararg eventTypes: Event, endExclusive: Int): EventWithIndex? { + return findEventAndIndex(eventTypes, endExclusive) + } + + override fun indexOfLast(vararg eventTypes: Event, endExclusive: Int): Int? { + return findEventAndIndex(eventTypes, endExclusive)?.eventIndex + } + + override fun popFromEnd(vararg eventTypes: Event) { + takeFromEnd(*eventTypes) + } + + override fun takeTail(vararg eventTypes: Event): List { + val eventWithIndex = this.findEventAndIndex(eventTypes) + + return if (eventWithIndex != null) { + this.removeAllAfterExclusive(eventWithIndex.eventIndex) + } else { + this.removeAllAfterInclusive(0) + } + } + + override fun takeAllAfterInclusive(endInclusive: Int): List { + return removeAllAfterInclusive(endInclusive) + } + + override fun takeFromEnd(vararg eventTypes: Event): GenericEvent.Instance? { + return findEventAndIndex(eventTypes)?.let { (event, index) -> + removeAllAfterInclusive(index) + + event + } + } + + private fun findEventAndIndex(eventTypes: Array, endExclusive: Int = this.events.size): EventWithIndex? { + val eventsQueue = this.events + val limit = endExclusive.coerceAtMost(eventsQueue.size) + + for (i in (limit - 1) downTo 0) { + val nextEvent = eventsQueue[i] + + eventTypes.forEach { event -> + if (nextEvent.instanceOf(event)) return EventWithIndex(nextEvent, i) + } + } + + return null + } + + private fun removeAllAfterInclusive(index: Int): List { + val subList = this.events.subList(index, this.events.size) + val subListCopy = subList.toList() + + subList.clear() + + return subListCopy + } + + private fun removeAllAfterExclusive(index: Int): List { + val subList = this.events.subList(index + 1, this.events.size) + val subListCopy = subList.toList() + + subList.clear() + + return subListCopy + } +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/NestedCallNode.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/NestedCallNode.kt new file mode 100644 index 0000000000..f71dbfd07a --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/NestedCallNode.kt @@ -0,0 +1,56 @@ +package io.novafoundation.nova.runtime.extrinsic.visitor.impl + +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisitor +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +internal interface NestedCallNode { + + fun canVisit(call: GenericCall.Instance): Boolean + + /** + * Calculates exclusive end index that is needed to skip all internal events related to this nested call + * For example, utility.batch supposed to skip BatchCompleted/BatchInterrupted and all ItemCompleted events + * This function is used by `visit` to skip internal events of nested nodes of the same type (batch inside batch or proxy inside proxy) + * so they wont interfere + * Should not be called on failed nested calls since they emit no events and its trivial to proceed + */ + fun endExclusiveToSkipInternalEvents(call: GenericCall.Instance, context: EventCountingContext): Int + + fun visit(call: GenericCall.Instance, context: VisitingContext) +} + +internal interface VisitingContext { + + val rootExtrinsic: ExtrinsicWithEvents + + val runtime: RuntimeSnapshot + + val origin: AccountId + + val callSucceeded: Boolean + + val visitor: ExtrinsicVisitor + + val logger: ExtrinsicVisitorLogger + + val eventQueue: MutableEventQueue + + fun nestedVisit(visit: ExtrinsicVisit) + + fun endExclusiveToSkipInternalEvents(call: GenericCall.Instance): Int +} + +internal interface EventCountingContext { + + val runtime: RuntimeSnapshot + + val eventQueue: EventQueue + + val endExclusive: Int + + fun endExclusiveToSkipInternalEvents(call: GenericCall.Instance, endExclusive: Int): Int +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt new file mode 100644 index 0000000000..c6f30c6b01 --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt @@ -0,0 +1,139 @@ +package io.novafoundation.nova.runtime.extrinsic.visitor.impl + +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisitor +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicWalk +import io.novafoundation.nova.runtime.extrinsic.visitor.impl.nodes.proxy.ProxyNode +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.getRuntime +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.isSuccess +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.signer +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall +import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAddress + +internal class RealExtrinsicWalk( + private val chainRegistry: ChainRegistry, + private val knownNodes: List = defaultNodes(), +) : ExtrinsicWalk { + + companion object { + + fun defaultNodes() = listOf(ProxyNode()) + } + + override suspend fun walk( + source: ExtrinsicWithEvents, + chainId: ChainId, + visitor: ExtrinsicVisitor + ) { + val runtime = chainRegistry.getRuntime(chainId) + + val rootVisit = ExtrinsicVisit( + rootExtrinsic = source, + call = source.extrinsic.call, + success = source.isSuccess(), + events = source.events.asReversed(), // Extrinsic Walk expects events to go from latest to newest however `ExtrinsicWithEvents` has them in opposite order + origin = source.extrinsic.signer() + ) + + nestedVisit(runtime, visitor, rootVisit, depth = 0) + } + + private fun nestedVisit( + runtime: RuntimeSnapshot, + visitor: ExtrinsicVisitor, + visitedCall: ExtrinsicVisit, + depth: Int, + ) { + val nestedNode = findNestedNode(visitedCall.call) + + if (nestedNode == null) { + val call = visitedCall.call + val display = "${call.module.name}.${call.function.name}" + val origin = visitedCall.origin + val newLogger = IndentVisitorLogger(indent = depth + 1) + + newLogger.info("Visiting leaf: ${display}, success: ${visitedCall.success}, origin: ${origin.toAddress(42)}") + + // Reverse events back to hide previously done reverse from clients + val withReversedEvents = visitedCall.copy(events = visitedCall.events.asReversed()) + + visitor.visit(withReversedEvents) + } else { + val eventQueue = RealEventQueue(visitedCall.events) + val newLogger = IndentVisitorLogger(indent = depth) + + val context = RealVisitingContext( + rootExtrinsic = visitedCall.rootExtrinsic, + eventsSize = visitedCall.events.size, + depth = depth, + runtime = runtime, + origin = visitedCall.origin, + callSucceeded = visitedCall.success, + visitor = visitor, + logger = newLogger, + eventQueue = eventQueue + ) + + nestedNode.visit(visitedCall.call, context) + } + } + + private fun endExclusiveToSkipInternalEvents( + runtime: RuntimeSnapshot, + call: GenericCall.Instance, + eventQueue: EventQueue, + endExclusive: Int, + ): Int { + val nestedNode = this.findNestedNode(call) + + return if (nestedNode != null) { + val context: EventCountingContext = RealEventCountingContext(runtime, eventQueue, endExclusive) + + nestedNode.endExclusiveToSkipInternalEvents(call, context) + } else { + // no internal events to skip since its a leaf + endExclusive + } + } + + private fun findNestedNode(call: GenericCall.Instance): NestedCallNode? { + return knownNodes.find { it.canVisit(call) } + } + + private inner class RealVisitingContext( + private val eventsSize: Int, + private val depth: Int, + override val rootExtrinsic: ExtrinsicWithEvents, + override val runtime: RuntimeSnapshot, + override val origin: AccountId, + override val callSucceeded: Boolean, + override val visitor: ExtrinsicVisitor, + override val logger: ExtrinsicVisitorLogger, + override val eventQueue: MutableEventQueue + ) : VisitingContext { + + override fun nestedVisit(visit: ExtrinsicVisit) { + return this@RealExtrinsicWalk.nestedVisit(runtime, visitor, visit, depth + 1) + } + + override fun endExclusiveToSkipInternalEvents(call: GenericCall.Instance): Int { + return this@RealExtrinsicWalk.endExclusiveToSkipInternalEvents(runtime, call, eventQueue, endExclusive = eventsSize) + } + } + + private inner class RealEventCountingContext( + override val runtime: RuntimeSnapshot, + override val eventQueue: EventQueue, + override val endExclusive: Int + ) : EventCountingContext { + + override fun endExclusiveToSkipInternalEvents(call: GenericCall.Instance, endExclusive: Int): Int { + return this@RealExtrinsicWalk.endExclusiveToSkipInternalEvents(runtime, call, eventQueue, endExclusive) + } + } +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt new file mode 100644 index 0000000000..d83055e27a --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt @@ -0,0 +1,105 @@ +package io.novafoundation.nova.runtime.extrinsic.visitor.impl.nodes.proxy + +import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountIdentifier +import io.novafoundation.nova.common.data.network.runtime.binding.bindGenericCall +import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.common.utils.proxy +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit +import io.novafoundation.nova.runtime.extrinsic.visitor.impl.EventCountingContext +import io.novafoundation.nova.runtime.extrinsic.visitor.impl.NestedCallNode +import io.novafoundation.nova.runtime.extrinsic.visitor.impl.VisitingContext +import io.novafoundation.nova.runtime.extrinsic.visitor.impl.peekItemFromEndOrThrow +import io.novafoundation.nova.runtime.extrinsic.visitor.impl.takeFromEndOrThrow +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericEvent +import jp.co.soramitsu.fearless_utils.runtime.metadata.event +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Event + +internal class ProxyNode : NestedCallNode { + + private val proxyCalls = arrayOf("proxy", "proxyAnnounced") + + + override fun canVisit(call: GenericCall.Instance): Boolean { + return call.module.name == Modules.PROXY && call.function.name in proxyCalls + } + + override fun endExclusiveToSkipInternalEvents(call: GenericCall.Instance, context: EventCountingContext): Int { + var endExclusive = context.endExclusive + val completionEventType = context.runtime.proxyCompletionEvent() + + val (completionEvent, completionIdx) = context.eventQueue.peekItemFromEndOrThrow(completionEventType, endExclusive = endExclusive) + endExclusive = completionIdx + + if (completionEvent.isProxySucceeded()) { + val (innerCall) = this.callAndOriginFromProxy(call) + endExclusive = context.endExclusiveToSkipInternalEvents(innerCall, endExclusive) + } + + return endExclusive + } + + override fun visit(call: GenericCall.Instance, context: VisitingContext) { + if (!context.callSucceeded) { + this.visitFailedProxyCall(call, context) + context.logger.info("Proxy: reverted by outer parent") + return + } + + val completionEventType = context.runtime.proxyCompletionEvent() + val completionEvent = context.eventQueue.takeFromEndOrThrow(completionEventType) + + if (completionEvent.isProxySucceeded()) { + context.logger.info("Proxy: execution succeeded") + + this.visitSucceededProxyCall(call, context) + } else { + context.logger.info("Proxy: execution failed") + + this.visitFailedProxyCall(call, context) + } + } + + private fun visitFailedProxyCall(call: GenericCall.Instance, context: VisitingContext) { + this.visitProxyCall(call, context, success = false, events = emptyList()) + } + + private fun visitSucceededProxyCall(call: GenericCall.Instance, context: VisitingContext) { + this.visitProxyCall(call, context, success = true, events = context.eventQueue.all()) + } + + private fun visitProxyCall( + call: GenericCall.Instance, + context: VisitingContext, + success: Boolean, + events: List + ) { + val (innerCall, innerOrigin) = this.callAndOriginFromProxy(call) + + val visit = ExtrinsicVisit( + rootExtrinsic = context.rootExtrinsic, + call = innerCall, + success = success, + events = events, + origin = innerOrigin + ) + + context.nestedVisit(visit) + } + + + private fun GenericEvent.Instance.isProxySucceeded(): Boolean { + return arguments.first().castToDictEnum().name == "Ok" + } + + private fun callAndOriginFromProxy(proxyCall: GenericCall.Instance): Pair { + return bindGenericCall(proxyCall.arguments["call"]) to bindAccountIdentifier(proxyCall.arguments["real"]) + } + + private fun RuntimeSnapshot.proxyCompletionEvent(): Event { + return metadata.proxy().event("ProxyExecuted") + } +} diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/repository/ExtrinsicWithEvents.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/repository/ExtrinsicWithEvents.kt index 957a4dafc8..95dd5b6dc4 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/repository/ExtrinsicWithEvents.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/repository/ExtrinsicWithEvents.kt @@ -1,8 +1,10 @@ package io.novafoundation.nova.runtime.multiNetwork.runtime.repository +import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountIdentifier import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.common.utils.instanceOf +import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericEvent import java.math.BigInteger @@ -30,21 +32,43 @@ fun ExtrinsicWithEvents.status(): ExtrinsicStatus? { } } -fun ExtrinsicWithEvents.nativeFee(): BigInteger? { +fun ExtrinsicWithEvents.isSuccess(): Boolean { + val status = requireNotNull(status()) { + "Not able to identify extrinsic status" + } + + return status == ExtrinsicStatus.SUCCESS +} + +fun Extrinsic.DecodedInstance.signer(): AccountId { + val accountIdentifier = requireNotNull(signature?.accountIdentifier) { + "Extrinsic is unsigned" + } + + return bindAccountIdentifier(accountIdentifier) +} + +fun List.nativeFee(): BigInteger? { val event = findEvent(Modules.TRANSACTION_PAYMENT, "TransactionFeePaid") ?: return null val (_, actualFee, tip) = event.arguments return bindNumber(actualFee) + bindNumber(tip) } -fun ExtrinsicWithEvents.findEvent(module: String, event: String): GenericEvent.Instance? { - return events.find { it.instanceOf(module, event) } +fun List.requireNativeFee(): BigInteger { + return requireNotNull(nativeFee()) { + "No native fee event found" + } +} + +fun List.findEvent(module: String, event: String): GenericEvent.Instance? { + return find { it.instanceOf(module, event) } } -fun ExtrinsicWithEvents.hasEvent(module: String, event: String): Boolean { - return events.any { it.instanceOf(module, event) } +fun List.hasEvent(module: String, event: String): Boolean { + return any { it.instanceOf(module, event) } } -fun ExtrinsicWithEvents.findAllEvents(module: String, event: String): List { - return events.filter { it.instanceOf(module, event) } +fun List.findAllOfType(module: String, event: String): List { + return filter { it.instanceOf(module, event) } } diff --git a/runtime/src/test/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ProxyWalkTest.kt b/runtime/src/test/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ProxyWalkTest.kt new file mode 100644 index 0000000000..bdf59d0adc --- /dev/null +++ b/runtime/src/test/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ProxyWalkTest.kt @@ -0,0 +1,330 @@ +package io.novafoundation.nova.runtime.extrinsic.visitor.impl + +import io.novafoundation.nova.common.data.network.runtime.binding.MultiAddress +import io.novafoundation.nova.common.data.network.runtime.binding.bindMultiAddress +import io.novafoundation.nova.runtime.ext.Geneses +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicVisit +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicWalk +import io.novafoundation.nova.runtime.extrinsic.visitor.api.walkToList +import io.novafoundation.nova.runtime.extrinsic.visitor.impl.nodes.proxy.ProxyNode +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.runtime.RuntimeProvider +import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.ExtrinsicWithEvents +import io.novafoundation.nova.test_shared.any +import io.novafoundation.nova.test_shared.whenever +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.DictEnum +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericEvent +import jp.co.soramitsu.fearless_utils.runtime.metadata.RuntimeMetadata +import jp.co.soramitsu.fearless_utils.runtime.metadata.call +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Event +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.MetadataFunction +import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Module +import junit.framework.Assert.assertEquals +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertArrayEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.junit.MockitoJUnitRunner +import java.math.BigInteger + +@RunWith(MockitoJUnitRunner::class) +internal class ProxyWalkTest { + + @Mock + private lateinit var runtimeProvider: RuntimeProvider + + @Mock + private lateinit var chainRegistry: ChainRegistry + + @Mock + private lateinit var runtime: RuntimeSnapshot + + @Mock + private lateinit var metadata: RuntimeMetadata + + @Mock + private lateinit var proxyModule: Module + + private lateinit var extrinsicWalk: ExtrinsicWalk + + private val proxyExecutedType = Event("ProxyExecuted", index = 0 to 0, documentation = emptyList(), arguments = emptyList()) + + private val proxy = byteArrayOf(0x00) + private val proxied = byteArrayOf(0x01) + + private val testModule = createTestModuleWithCall(moduleName = "Test", callName = "test") + private val testInnerCall = GenericCall.Instance( + module = testModule, + function = testModule.call("test"), + arguments = emptyMap() + ) + + @Before + fun setup() = runBlocking { + whenever(proxyModule.events).thenReturn(mapOf("ProxyExecuted" to proxyExecutedType)) + whenever(metadata.modules).thenReturn(mapOf("Proxy" to proxyModule)) + whenever(runtime.metadata).thenReturn(metadata) + whenever(runtimeProvider.get()).thenReturn(runtime) + whenever(chainRegistry.getRuntimeProvider(any())).thenReturn(runtimeProvider) + + extrinsicWalk = RealExtrinsicWalk(chainRegistry, knownNodes = listOf(ProxyNode())) + } + + @Test + fun shouldVisitSucceededSimpleCall() = runBlocking { + val events = listOf(extrinsicSuccess(), testEvent()) + + val extrinsic = createExtrinsic( + signer = proxied, + call = testInnerCall, + events = events + ) + + val visit = extrinsicWalk.walkSingle(extrinsic) + assertEquals(true, visit.success) + assertArrayEquals(proxied, visit.origin) + assertEquals(testInnerCall, visit.call) + assertEquals(events, visit.events) + } + + @Test + fun shouldVisitFailedSimpleCall() = runBlocking { + val events = listOf(extrinsicFailed()) + + val extrinsic = createExtrinsic( + signer = proxied, + call = testInnerCall, + events = events + ) + + val visit = extrinsicWalk.walkSingle(extrinsic) + assertEquals(false, visit.success) + assertArrayEquals(proxied, visit.origin) + assertEquals(testInnerCall, visit.call) + assertEquals(events, visit.events) + } + + @Test + fun shouldVisitSucceededSingleProxyCall() = runBlocking { + val innerProxyEvents = listOf(testEvent()) + val events = listOf(extrinsicSuccess(), proxyExecuted(success = true)) + innerProxyEvents + + val extrinsic = createExtrinsic( + signer = proxy, + call = proxyCall( + real = proxied, + innerCall = testInnerCall + ), + events = events + ) + + val visit = extrinsicWalk.walkSingle(extrinsic) + assertEquals(true, visit.success) + assertArrayEquals(proxied, visit.origin) + assertEquals(testInnerCall, visit.call) + assertEquals(innerProxyEvents, visit.events) + } + + @Test + fun shouldVisitFailedSingleProxyCall() = runBlocking { + val innerProxyEvents = emptyList() + val events = listOf(extrinsicSuccess(), proxyExecuted(success = false)) + + val extrinsic = createExtrinsic( + signer = proxy, + call = proxyCall( + real = proxied, + innerCall = testInnerCall + ), + events = events + ) + + val visit = extrinsicWalk.walkSingle(extrinsic) + assertEquals(false, visit.success) + assertArrayEquals(proxied, visit.origin) + assertEquals(testInnerCall, visit.call) + assertEquals(innerProxyEvents, visit.events) + } + + @Test + fun shouldVisitSucceededMultipleProxyCalls() = runBlocking { + val innerProxyEvents = listOf(testEvent()) + val events = listOf(extrinsicSuccess(), proxyExecuted(success = true), proxyExecuted(success = true), proxyExecuted(success = true)) + innerProxyEvents + + val proxy1 = byteArrayOf(0x00) + val proxy2 = byteArrayOf(0x01) + val proxy3 = byteArrayOf(0x02) + val proxied = byteArrayOf(0x10) + + val extrinsic = createExtrinsic( + signer = proxy1, + call = proxyCall( + real = proxy2, + innerCall = proxyCall( + real = proxy3, + innerCall = proxyCall( + real = proxied, + innerCall = testInnerCall + ) + ) + ), + events = events + ) + + val visit = extrinsicWalk.walkSingle(extrinsic) + assertEquals(true, visit.success) + assertArrayEquals(proxied, visit.origin) + assertEquals(testInnerCall, visit.call) + assertEquals(innerProxyEvents, visit.events) + } + + @Test + fun shouldVisitFailedMultipleProxyCalls() = runBlocking { + val innerProxyEvents = emptyList() + val events = listOf(extrinsicSuccess(), proxyExecuted(success = false)) // only outer-most proxy succeeded + + val proxy1 = byteArrayOf(0x00) + val proxy2 = byteArrayOf(0x01) + val proxy3 = byteArrayOf(0x02) + val proxied = byteArrayOf(0x10) + + val extrinsic = createExtrinsic( + signer = proxy1, + call = proxyCall( + real = proxy2, + innerCall = proxyCall( + real = proxy3, + innerCall = proxyCall( + real = proxied, + innerCall = testInnerCall + ) + ) + ), + events = events + ) + + val visit = extrinsicWalk.walkSingle(extrinsic) + assertEquals(false, visit.success) + assertArrayEquals(proxied, visit.origin) + assertEquals(testInnerCall, visit.call) + assertEquals(innerProxyEvents, visit.events) + } + + private suspend fun ExtrinsicWalk.walkToList(extrinsicWithEvents: ExtrinsicWithEvents): List { + return walkToList(extrinsicWithEvents, Chain.Geneses.POLKADOT) + } + + private suspend fun ExtrinsicWalk.walkSingle(extrinsicWithEvents: ExtrinsicWithEvents): ExtrinsicVisit { + val visits = walkToList(extrinsicWithEvents, Chain.Geneses.POLKADOT) + assertEquals(1, visits.size) + + return visits.single() + } + + private fun createExtrinsic( + signer: AccountId, + call: GenericCall.Instance, + events: List + ) = ExtrinsicWithEvents( + extrinsic = Extrinsic.DecodedInstance( + signature = Extrinsic.Signature( + accountIdentifier = bindMultiAddress(MultiAddress.Id(signer)), + signature = null, + signedExtras = emptyMap() + ), + call = call, + ), + extrinsicHash = "0x", + events = events + ) + + private fun createTestModuleWithCall( + moduleName: String, + callName: String + ): Module { + return Module( + name = moduleName, + storage = null, + calls = mapOf( + callName to MetadataFunction( + name = callName, + arguments = emptyList(), + documentation = emptyList(), + index = 0 to 0 + ) + ), + events = emptyMap(), + constants = emptyMap(), + errors = emptyMap(), + index = BigInteger.ZERO + ) + } + + private fun extrinsicSuccess(): GenericEvent.Instance { + return mockEvent("System", "ExtrinsicSuccess") + } + + private fun extrinsicFailed(): GenericEvent.Instance { + return mockEvent("System", "ExtrinsicFailed") + } + + private fun proxyExecuted(success: Boolean): GenericEvent.Instance { + val outcomeVariant = if (success) "Ok" else "Err" + val outcome = DictEnum.Entry(name = outcomeVariant, value = null) + + return GenericEvent.Instance(proxyModule, proxyExecutedType, arguments = listOf(outcome)) + } + + + private fun testEvent(): GenericEvent.Instance { + return mockEvent(testModule.name, "test") + } + + private fun proxyCall(real: AccountId, innerCall: GenericCall.Instance): GenericCall.Instance { + return mockCall( + moduleName = "Proxy", + callName = "proxy", + arguments = mapOf( + "real" to bindMultiAddress(MultiAddress.Id(real)), + "call" to innerCall, + // other args are not relevant + ) + ) + } + + private fun mockEvent(moduleName: String, eventName: String, arguments: List = emptyList()): GenericEvent.Instance { + val module = Mockito.mock(Module::class.java) + whenever(module.name).thenReturn(moduleName) + + val event = Mockito.mock(Event::class.java) + whenever(event.name).thenReturn(eventName) + + return GenericEvent.Instance( + module = module, + event = event, + arguments = arguments + ) + } + + private fun mockCall(moduleName: String, callName: String, arguments: Map = emptyMap()): GenericCall.Instance { + val module = Mockito.mock(Module::class.java) + whenever(module.name).thenReturn(moduleName) + + val function = Mockito.mock(MetadataFunction::class.java) + whenever(function.name).thenReturn(callName) + + return GenericCall.Instance( + module = module, + function = function, + arguments = arguments + ) + } +} From 4c312151f6e52938cea80b952768b78f0c0868c1 Mon Sep 17 00:00:00 2001 From: Valentun Date: Tue, 19 Dec 2023 18:32:37 +0300 Subject: [PATCH 048/100] Fixes --- .../substrate/RealSubstrateRealtimeOperationFetcher.kt | 4 ++-- .../blockchain/assets/history/utility/NativeAssetHistory.kt | 2 +- .../feature_wallet_impl/di/WalletFeatureDependencies.kt | 3 +++ .../nova/feature_wallet_impl/di/WalletFeatureModule.kt | 6 ++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/RealSubstrateRealtimeOperationFetcher.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/RealSubstrateRealtimeOperationFetcher.kt index 2d57cdd6ac..fc402d3409 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/RealSubstrateRealtimeOperationFetcher.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/realtime/substrate/RealSubstrateRealtimeOperationFetcher.kt @@ -14,13 +14,13 @@ import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.EventsRepo internal class SubstrateRealtimeOperationFetcherFactory( private val multiLocationConverterFactory: MultiLocationConverterFactory, private val eventsRepository: EventsRepository, - private val callWalk: ExtrinsicWalk, + private val extrinsicWalk: ExtrinsicWalk, ) : Factory { override fun create(sources: List): SubstrateRealtimeOperationFetcher { val extractors = sources.map { it.extractor() } - return RealSubstrateRealtimeOperationFetcher(eventsRepository, extractors, callWalk) + return RealSubstrateRealtimeOperationFetcher(eventsRepository, extractors, extrinsicWalk) } private fun Factory.Source.extractor(): Extractor { diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/utility/NativeAssetHistory.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/utility/NativeAssetHistory.kt index 7690743ce3..67be5eede8 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/utility/NativeAssetHistory.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/history/utility/NativeAssetHistory.kt @@ -61,7 +61,7 @@ class NativeAssetHistory( val amount = bindNumber(call.arguments["value"]) return RealtimeHistoryUpdate.Type.Transfer( - senderId = bindAccountIdentifier(extrinsicVisit.origin), + senderId = extrinsicVisit.origin, recipientId = bindAccountIdentifier(call.arguments["dest"]), amountInPlanks = amount, chainAsset = chainAsset, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureDependencies.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureDependencies.kt index 4ebd2ef179..85d2a245e9 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureDependencies.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureDependencies.kt @@ -38,6 +38,7 @@ import io.novafoundation.nova.feature_currency_api.domain.interfaces.CurrencyRep import io.novafoundation.nova.runtime.di.LOCAL_STORAGE_SOURCE import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE import io.novafoundation.nova.runtime.ethereum.StorageSharedRequestsBuilderFactory +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicWalk import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.multiLocation.converter.MultiLocationConverterFactory import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.EventsRepository @@ -141,4 +142,6 @@ interface WalletFeatureDependencies { val computationalCache: ComputationalCache val multiLocationConverterFactory: MultiLocationConverterFactory + + val extrinsicWalk: ExtrinsicWalk } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureModule.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureModule.kt index a750f348ee..12181976f1 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureModule.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureModule.kt @@ -74,6 +74,7 @@ import io.novafoundation.nova.feature_wallet_impl.data.source.CoingeckoCoinPrice import io.novafoundation.nova.feature_wallet_impl.data.storage.TransferCursorStorage import io.novafoundation.nova.feature_wallet_impl.domain.RealCrossChainTransfersUseCase import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE +import io.novafoundation.nova.runtime.extrinsic.visitor.api.ExtrinsicWalk import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.multiLocation.converter.MultiLocationConverterFactory import io.novafoundation.nova.runtime.multiNetwork.runtime.repository.EventsRepository @@ -328,8 +329,9 @@ class WalletFeatureModule { @FeatureScope fun provideSubstrateRealtimeOperationFetcherFactory( multiLocationConverterFactory: MultiLocationConverterFactory, - eventsRepository: EventsRepository + eventsRepository: EventsRepository, + extrinsicWalk: ExtrinsicWalk ): SubstrateRealtimeOperationFetcher.Factory { - return SubstrateRealtimeOperationFetcherFactory(multiLocationConverterFactory, eventsRepository) + return SubstrateRealtimeOperationFetcherFactory(multiLocationConverterFactory, eventsRepository, extrinsicWalk) } } From eadfa437d63c8c1a7311d965f97ef23afcf0a287 Mon Sep 17 00:00:00 2001 From: Valentun Date: Tue, 19 Dec 2023 18:33:42 +0300 Subject: [PATCH 049/100] Code style --- .../common/data/network/runtime/binding/GenericCall.kt | 2 +- .../data/ethereum/transaction/RealEvmTransactionService.kt | 2 -- .../extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt | 2 +- .../runtime/extrinsic/visitor/impl/MutableEventQueue.kt | 3 +-- .../runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt | 7 ++++--- .../extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt | 2 -- .../storage/source/query/BaseStorageQueryContext.kt | 1 - 7 files changed, 7 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/GenericCall.kt b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/GenericCall.kt index bfdb14ce9f..146a0fe1a6 100644 --- a/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/GenericCall.kt +++ b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/GenericCall.kt @@ -2,6 +2,6 @@ package io.novafoundation.nova.common.data.network.runtime.binding import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall -fun bindGenericCall(decoded: Any?) : GenericCall.Instance { +fun bindGenericCall(decoded: Any?): GenericCall.Instance { return decoded.cast() } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index b1f6fc6efc..28a556bd9e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -23,8 +23,6 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow -import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw import org.web3j.crypto.RawTransaction diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt index a6fb78c889..53d8300788 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ExtrinsicVisitorLogger.kt @@ -17,7 +17,7 @@ internal class IndentVisitorLogger( private val indentPrefix = " ".repeat(indent) override fun info(message: String) { - Log.d(tag, indentPrefix + message) + Log.d(tag, indentPrefix + message) } override fun error(message: String) { diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/MutableEventQueue.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/MutableEventQueue.kt index 64bc37144e..5c390069b3 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/MutableEventQueue.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/MutableEventQueue.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.runtime.extrinsic.visitor.impl; +package io.novafoundation.nova.runtime.extrinsic.visitor.impl import io.novafoundation.nova.common.utils.instanceOf import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericEvent @@ -52,7 +52,6 @@ internal inline fun MutableEventQueue.takeFromEndOrThrow(vararg eventTypes: Even } } - data class EventWithIndex(val event: GenericEvent.Instance, val eventIndex: Int) class RealEventQueue(event: List) : MutableEventQueue { diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt index c6f30c6b01..6627e1df1c 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt @@ -36,7 +36,8 @@ internal class RealExtrinsicWalk( rootExtrinsic = source, call = source.extrinsic.call, success = source.isSuccess(), - events = source.events.asReversed(), // Extrinsic Walk expects events to go from latest to newest however `ExtrinsicWithEvents` has them in opposite order + // Extrinsic Walk expects events to go from latest to newest however `ExtrinsicWithEvents` has them in opposite order + events = source.events.asReversed(), origin = source.extrinsic.signer() ) @@ -57,7 +58,7 @@ internal class RealExtrinsicWalk( val origin = visitedCall.origin val newLogger = IndentVisitorLogger(indent = depth + 1) - newLogger.info("Visiting leaf: ${display}, success: ${visitedCall.success}, origin: ${origin.toAddress(42)}") + newLogger.info("Visiting leaf: $display, success: ${visitedCall.success}, origin: ${origin.toAddress(42)}") // Reverse events back to hide previously done reverse from clients val withReversedEvents = visitedCall.copy(events = visitedCall.events.asReversed()) @@ -122,7 +123,7 @@ internal class RealExtrinsicWalk( } override fun endExclusiveToSkipInternalEvents(call: GenericCall.Instance): Int { - return this@RealExtrinsicWalk.endExclusiveToSkipInternalEvents(runtime, call, eventQueue, endExclusive = eventsSize) + return this@RealExtrinsicWalk.endExclusiveToSkipInternalEvents(runtime, call, eventQueue, endExclusive = eventsSize) } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt index d83055e27a..bf0a7ced8c 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/nodes/proxy/ProxyNode.kt @@ -22,7 +22,6 @@ internal class ProxyNode : NestedCallNode { private val proxyCalls = arrayOf("proxy", "proxyAnnounced") - override fun canVisit(call: GenericCall.Instance): Boolean { return call.module.name == Modules.PROXY && call.function.name in proxyCalls } @@ -90,7 +89,6 @@ internal class ProxyNode : NestedCallNode { context.nestedVisit(visit) } - private fun GenericEvent.Instance.isProxySucceeded(): Boolean { return arguments.first().castToDictEnum().name == "Ok" } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt index 9e1c2ab84e..5be9f7d720 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt @@ -7,7 +7,6 @@ import io.novafoundation.nova.common.data.network.runtime.binding.fromHexOrIncom import io.novafoundation.nova.common.data.network.runtime.binding.incompatible import io.novafoundation.nova.common.utils.ComponentHolder import io.novafoundation.nova.common.utils.mapValuesNotNull -import io.novafoundation.nova.common.utils.splitKeyToComponents import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilderImpl From 48b89743a0d56459552ab3cd675cdc617c33bd6f Mon Sep 17 00:00:00 2001 From: Valentun Date: Tue, 19 Dec 2023 19:20:13 +0300 Subject: [PATCH 050/100] Fixes --- .../runtime/extrinsic/visitor/api/ExtrinsicWalk.kt | 2 +- .../extrinsic/visitor/impl/RealExtrinsicWalk.kt | 8 ++------ .../runtime/extrinsic/visitor/impl/ProxyWalkTest.kt | 10 +++++----- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalk.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalk.kt index 3931d71eb0..e94225b5d1 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalk.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/api/ExtrinsicWalk.kt @@ -20,7 +20,7 @@ fun interface ExtrinsicVisitor { fun visit(visit: ExtrinsicVisit) } -data class ExtrinsicVisit( +class ExtrinsicVisit( /** * Whole extrinsic object. Useful for accessing data outside if the current visit scope, e.g. some top-level events diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt index 6627e1df1c..65a5064519 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/RealExtrinsicWalk.kt @@ -36,8 +36,7 @@ internal class RealExtrinsicWalk( rootExtrinsic = source, call = source.extrinsic.call, success = source.isSuccess(), - // Extrinsic Walk expects events to go from latest to newest however `ExtrinsicWithEvents` has them in opposite order - events = source.events.asReversed(), + events = source.events, origin = source.extrinsic.signer() ) @@ -60,10 +59,7 @@ internal class RealExtrinsicWalk( newLogger.info("Visiting leaf: $display, success: ${visitedCall.success}, origin: ${origin.toAddress(42)}") - // Reverse events back to hide previously done reverse from clients - val withReversedEvents = visitedCall.copy(events = visitedCall.events.asReversed()) - - visitor.visit(withReversedEvents) + visitor.visit(visitedCall) } else { val eventQueue = RealEventQueue(visitedCall.events) val newLogger = IndentVisitorLogger(indent = depth) diff --git a/runtime/src/test/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ProxyWalkTest.kt b/runtime/src/test/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ProxyWalkTest.kt index bdf59d0adc..b939390bcd 100644 --- a/runtime/src/test/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ProxyWalkTest.kt +++ b/runtime/src/test/java/io/novafoundation/nova/runtime/extrinsic/visitor/impl/ProxyWalkTest.kt @@ -80,7 +80,7 @@ internal class ProxyWalkTest { @Test fun shouldVisitSucceededSimpleCall() = runBlocking { - val events = listOf(extrinsicSuccess(), testEvent()) + val events = listOf(testEvent(), extrinsicSuccess()) val extrinsic = createExtrinsic( signer = proxied, @@ -115,7 +115,7 @@ internal class ProxyWalkTest { @Test fun shouldVisitSucceededSingleProxyCall() = runBlocking { val innerProxyEvents = listOf(testEvent()) - val events = listOf(extrinsicSuccess(), proxyExecuted(success = true)) + innerProxyEvents + val events = innerProxyEvents + listOf(proxyExecuted(success = true), extrinsicSuccess()) val extrinsic = createExtrinsic( signer = proxy, @@ -136,7 +136,7 @@ internal class ProxyWalkTest { @Test fun shouldVisitFailedSingleProxyCall() = runBlocking { val innerProxyEvents = emptyList() - val events = listOf(extrinsicSuccess(), proxyExecuted(success = false)) + val events = listOf(proxyExecuted(success = false), extrinsicSuccess()) val extrinsic = createExtrinsic( signer = proxy, @@ -157,7 +157,7 @@ internal class ProxyWalkTest { @Test fun shouldVisitSucceededMultipleProxyCalls() = runBlocking { val innerProxyEvents = listOf(testEvent()) - val events = listOf(extrinsicSuccess(), proxyExecuted(success = true), proxyExecuted(success = true), proxyExecuted(success = true)) + innerProxyEvents + val events = innerProxyEvents + listOf(proxyExecuted(success = true), proxyExecuted(success = true), proxyExecuted(success = true), extrinsicSuccess()) val proxy1 = byteArrayOf(0x00) val proxy2 = byteArrayOf(0x01) @@ -189,7 +189,7 @@ internal class ProxyWalkTest { @Test fun shouldVisitFailedMultipleProxyCalls() = runBlocking { val innerProxyEvents = emptyList() - val events = listOf(extrinsicSuccess(), proxyExecuted(success = false)) // only outer-most proxy succeeded + val events = listOf(proxyExecuted(success = false), proxyExecuted(success = true), extrinsicSuccess()) // only outer-most proxy succeeded val proxy1 = byteArrayOf(0x00) val proxy2 = byteArrayOf(0x01) From 07d3377cc45d6a5332ff31cd0351c81652d875ad Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 19 Dec 2023 17:25:06 +0100 Subject: [PATCH 051/100] Implemented proxy fee validation --- common/src/main/res/values/strings.xml | 4 + .../nova/core_db/dao/MetaAccountDao.kt | 15 ++++ .../domain/interfaces/AccountRepository.kt | 2 + .../account/proxy/ProxySigningPresenter.kt | 7 ++ feature-account-impl/build.gradle | 1 + .../data/repository/AccountRepositoryImpl.kt | 5 ++ .../datasource/AccountDataSource.kt | 2 + .../datasource/AccountDataSourceImpl.kt | 4 + .../data/signer/proxy/ProxiedSigner.kt | 39 +++++++++- .../ProxiedExtrinsicValidationSystem.kt | 31 ++++++++ .../ProxyHaveEnoughFeeValidation.kt | 70 +++++++++++++++++ .../ProxyHaveEnoughFeeValidation2.kt | 77 +++++++++++++++++++ .../di/AccountFeatureComponent.kt | 2 + .../di/AccountFeatureDependencies.kt | 6 ++ .../di/AccountFeatureHolder.kt | 2 + .../di/AccountFeatureModule.kt | 2 +- .../di/modules/signers/ProxiedSignerModule.kt | 73 ++++++++++++++++++ .../di/modules/{ => signers}/SignersModule.kt | 14 +--- .../account/common/listing/ProxyFormatter.kt | 2 +- .../proxy/sign/RealProxySigningPresenter.kt | 38 +++++++++ 20 files changed, 378 insertions(+), 18 deletions(-) create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxiedExtrinsicValidationSystem.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation2.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt rename feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/{ => signers}/SignersModule.kt (92%) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 584297f67e..f57d700067 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,6 +1,10 @@ + Unknown error + Delegated account % doesn’t have enough balance to pay the network fee of %. Available balance to pay fee: % + Not enough tokens to pay the fee + Do not show this again This is Delegating (Proxied) account diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 7f86c3ada6..0e107d1bad 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -188,6 +188,21 @@ interface MetaAccountDao { @Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)") suspend fun changeAccountsStatus(metaIds: List, status: MetaAccountLocal.Status) + + @Query(""" + WITH RECURSIVE Chain AS ( + SELECT pa.proxyMetaId + FROM proxy_accounts pa + WHERE pa.proxiedMetaId = :proxiedMetaId + UNION ALL + SELECT pa.proxyMetaId + FROM proxy_accounts pa + INNER JOIN Chain c ON c.proxyMetaId = pa.proxiedMetaId + ) + SELECT MAX(proxyMetaId) as finalMetaId + FROM Chain + """) + fun getLastProxyAccountId(proxiedMetaId: Long): Long? } class MetaAccountWithBalanceLocal( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index 795e064fdb..2fd57e7681 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -122,4 +122,6 @@ interface AccountRepository { ): String suspend fun isAccountExists(accountId: AccountId): Boolean + + suspend fun getLastProxyAccountFor(proxiedMetaId: Long): MetaAccount? } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt index 4abe3cdaf1..f6f808a909 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt @@ -1,7 +1,10 @@ package io.novafoundation.nova.feature_account_api.presenatation.account.proxy +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import java.math.BigInteger interface ProxySigningPresenter { @@ -10,4 +13,8 @@ interface ProxySigningPresenter { suspend fun notEnoughPermission(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount, proxyTypes: List) suspend fun signingIsNotSupported() + + suspend fun notEnoughFee(proxyMetaAccount: MetaAccount, asset: Chain.Asset, availableBalanceToPayFee: BigInteger, fee: Fee) + + suspend fun unsupportedValidationError() } diff --git a/feature-account-impl/build.gradle b/feature-account-impl/build.gradle index 7d09470dce..41ea4fa182 100644 --- a/feature-account-impl/build.gradle +++ b/feature-account-impl/build.gradle @@ -42,6 +42,7 @@ dependencies { implementation project(':feature-currency-api') implementation project(':feature-ledger-api') implementation project(':feature-versions-api') + implementation project(':feature-wallet-api') implementation project(':web3names') implementation kotlinDep diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index 30b81723a2..4acc4e0e19 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -255,6 +255,11 @@ class AccountRepositoryImpl( return accountDataSource.accountExists(accountId) } + override suspend fun getLastProxyAccountFor(proxiedMetaId: Long): MetaAccount? { + val lastProxyMetaId = accountDataSource.getLastProxyAccountId(proxiedMetaId) + return lastProxyMetaId?.let { accountDataSource.getMetaAccount(it) } + } + override fun nodesFlow(): Flow> { return nodeDao.nodesFlow() .mapList { mapNodeLocalToNode(it) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 269cc26a92..8dd2294b78 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -89,4 +89,6 @@ interface AccountDataSource : SecretStoreV1 { ) suspend fun hasMetaAccounts(): Boolean + + suspend fun getLastProxyAccountId(proxiedMetaId: Long): Long? } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 9cce988484..d4156c1ec6 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -271,6 +271,10 @@ class AccountDataSourceImpl( return metaAccountDao.hasMetaAccounts() } + override suspend fun getLastProxyAccountId(proxiedMetaId: Long): Long? { + return metaAccountDao.getLastProxyAccountId(proxiedMetaId) + } + private inline fun async(crossinline action: suspend () -> Unit) { GlobalScope.launch(Dispatchers.Default) { action() 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 c7ee95a352..64ce55b344 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 @@ -4,6 +4,7 @@ import io.novafoundation.nova.common.base.errors.SigningCancelledException import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 import io.novafoundation.nova.common.utils.chainId import io.novafoundation.nova.common.utils.toCallInstance +import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository @@ -11,6 +12,9 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationPayload +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationSystem import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic @@ -24,7 +28,8 @@ class ProxiedSignerFactory( private val chainRegistry: ChainRegistry, private val accountRepository: AccountRepository, private val proxySigningPresenter: ProxySigningPresenter, - private val proxyRepository: ProxyRepository + private val proxyRepository: ProxyRepository, + private val proxiedExtrinsicValidationSystem: ProxiedExtrinsicValidationSystem, ) { fun create(metaAccount: MetaAccount, signerProvider: SignerProvider): ProxiedSigner { @@ -34,7 +39,8 @@ class ProxiedSignerFactory( accountRepository, signerProvider, proxySigningPresenter, - proxyRepository + proxyRepository, + proxiedExtrinsicValidationSystem ) } } @@ -45,7 +51,8 @@ class ProxiedSigner( private val accountRepository: AccountRepository, private val signerProvider: SignerProvider, private val proxySigningPresenter: ProxySigningPresenter, - private val proxyRepository: ProxyRepository + private val proxyRepository: ProxyRepository, + private val proxiedExtrinsicValidationSystem: ProxiedExtrinsicValidationSystem, ) : Signer { override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { @@ -56,7 +63,11 @@ class ProxiedSigner( val delegate = createDelegate(proxyMetaAccount) val modifiedPayload = modifyPayload(proxyMetaAccount, payloadExtrinsic) - return delegate.signExtrinsic(modifiedPayload) + val signedExtrinsic = delegate.signExtrinsic(modifiedPayload) + + validateExtrinsic(signedExtrinsic) + + return signedExtrinsic } override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { @@ -67,6 +78,26 @@ class ProxiedSigner( return signerProvider.signerFor(proxyMetaAccount) } + private suspend fun validateExtrinsic(signedExtrinsic: SignedExtrinsic) { + val validationResult = proxiedExtrinsicValidationSystem.validate( + ProxiedExtrinsicValidationPayload( + proxiedMetaAccount, + chainRegistry.getChain(signedExtrinsic.payload.chainId), + signedExtrinsic.payload.call.toCallInstance()!!.call + ) + ) + + val validationStatus = validationResult.getOrNull() + if (validationStatus is ValidationStatus.NotValid) { + when (val reason = validationStatus.reason) { + is ProxyNotEnoughFee -> proxySigningPresenter.notEnoughFee(reason.proxyMetaAccount, reason.availableBalanceToPayFee, reason.fee) + else -> proxySigningPresenter.unsupportedValidationError() + } + + throw SigningCancelledException() + } + } + private suspend fun modifyPayload(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic): SignerPayloadExtrinsic { val availableProxyTypes = proxyRepository.getDelegatedProxyTypes( payload.chainId, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxiedExtrinsicValidationSystem.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxiedExtrinsicValidationSystem.kt new file mode 100644 index 0000000000..f85f2121df --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxiedExtrinsicValidationSystem.kt @@ -0,0 +1,31 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation + +import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry +import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import java.math.BigInteger +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + + +typealias ProxiedExtrinsicValidationSystem = ValidationSystem +typealias ProxiedExtrinsicValidationSystemBuilder = ValidationSystemBuilder + +class ProxiedExtrinsicValidationPayload(val rootProxiedMetaAccount: MetaAccount, val chain: Chain, val call: GenericCall.Instance) + +sealed interface ProxiedExtrinsicValidationFailure { + class ProxyNotEnoughFee(val proxyMetaAccount: MetaAccount, val availableBalanceToPayFee: BigInteger, val fee: Fee) : ProxiedExtrinsicValidationFailure + + object ProxyMetaAccountNotFound : ProxiedExtrinsicValidationFailure +} + +fun proxiedExtrinsicValidationSystem( + proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory +) = ValidationSystem { + proxyHaveEnoughFeeValidation(proxyHaveEnoughFeeValidationFactory) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation.kt new file mode 100644 index 0000000000..a7698b4c55 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation.kt @@ -0,0 +1,70 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation + +import io.novafoundation.nova.common.utils.atLeastZero +import io.novafoundation.nova.common.validation.Validation +import io.novafoundation.nova.common.validation.ValidationStatus +import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.common.validation.validOrError +import io.novafoundation.nova.common.validation.validationError +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyMetaAccountNotFound +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.runtime.ext.commissionAsset +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +class ProxyHaveEnoughFeeValidationFactory( + private val assetSourceRegistry: AssetSourceRegistry, + private val accountRepository: AccountRepository, + private val walletRepository: WalletRepository, + private val extrinsicService: ExtrinsicService +) { + fun create(): ProxyHaveEnoughFeeValidation { + return ProxyHaveEnoughFeeValidation( + assetSourceRegistry, + accountRepository, + walletRepository, + extrinsicService + ) + } +} + +class ProxyHaveEnoughFeeValidation( + private val assetSourceRegistry: AssetSourceRegistry, + private val accountRepository: AccountRepository, + private val walletRepository: WalletRepository, + private val extrinsicService: ExtrinsicService, +) : Validation { + + override suspend fun validate(value: ProxiedExtrinsicValidationPayload): ValidationStatus { + val lastProxyMetaAccount = accountRepository.getLastProxyAccountFor(value.rootProxiedMetaAccount.id) + if (lastProxyMetaAccount == null) { + return validationError(ProxyMetaAccountNotFound) + } + val commissionAsset = walletRepository.getAsset(lastProxyMetaAccount.id, value.chain.commissionAsset)!! + val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(value.chain, commissionAsset.token.configuration) + val fee = getFee(value) + val availableBalanceToPayFee = commissionAsset.balanceCountedTowardsEDInPlanks - existentialDeposit + + return validOrError(availableBalanceToPayFee >= fee.amount) { + ProxyNotEnoughFee(lastProxyMetaAccount, availableBalanceToPayFee.atLeastZero(), fee) + } + } + + private suspend fun getFee(value: ProxiedExtrinsicValidationPayload): Fee { + return extrinsicService.estimateFeeV2(value.chain) { + call(value.call) + } + } +} + +fun ProxiedExtrinsicValidationSystemBuilder.proxyHaveEnoughFeeValidation( + proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory +) = validate(proxyHaveEnoughFeeValidationFactory.create()) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation2.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation2.kt new file mode 100644 index 0000000000..2fe49a2d52 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation2.kt @@ -0,0 +1,77 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation + +import io.novafoundation.nova.common.utils.atLeastZero +import io.novafoundation.nova.common.validation.Validation +import io.novafoundation.nova.common.validation.ValidationStatus +import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.common.validation.validOrError +import io.novafoundation.nova.common.validation.validationError +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.runtime.ext.commissionAsset +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +class ProxyHaveEnoughFeeValidation2( + private val assetSourceRegistry: AssetSourceRegistry, + private val accountRepository: AccountRepository, + private val walletRepository: WalletRepository, + private val extrinsicService: ExtrinsicService, + private val proxiedMetaAccount: (P) -> MetaAccount, + private val chain: (P) -> Chain, + private val callInstance: (P) -> GenericCall.Instance, + private val proxyMetaAccountNotFound: () -> E, + private val proxyNotEnoughFee: (proxy: MetaAccount, availableBalanceToPayFee: Balance, fee: Fee) -> E, +) : Validation { + + override suspend fun validate(value: P): ValidationStatus { + val lastProxyMetaAccount = accountRepository.getLastProxyAccountFor(proxiedMetaAccount(value).id) + if (lastProxyMetaAccount == null) { + return validationError(proxyMetaAccountNotFound()) + } + val commissionAsset = walletRepository.getAsset(lastProxyMetaAccount.id, chain(value).commissionAsset)!! + val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chain(value), commissionAsset.token.configuration) + val fee = getFee(value) + val availableBalanceToPayFee = commissionAsset.balanceCountedTowardsEDInPlanks - existentialDeposit + + return validOrError(availableBalanceToPayFee >= fee.amount) { + proxyNotEnoughFee(lastProxyMetaAccount, availableBalanceToPayFee.atLeastZero(), fee) + } + } + + private suspend fun getFee(value: P): Fee { + return extrinsicService.estimateFeeV2(chain(value)) { + call(callInstance(value)) + } + } +} + +fun ValidationSystemBuilder.proxyHaveEnoughFeeValidation2( + assetSourceRegistry: AssetSourceRegistry, + accountRepository: AccountRepository, + walletRepository: WalletRepository, + extrinsicService: ExtrinsicService, + proxiedMetaAccount: (P) -> MetaAccount, + chain: (P) -> Chain, + callInstance: (P) -> GenericCall.Instance, + proxyMetaAccountNotFound: () -> E, + proxyNotEnoughFee: (proxy: MetaAccount, availableBalanceToPayFee: Balance, fee: Fee) -> E, +) = validate( + ProxyHaveEnoughFeeValidation2( + assetSourceRegistry, + accountRepository, + walletRepository, + extrinsicService, + proxiedMetaAccount, + chain, + callInstance, + proxyMetaAccountNotFound, + proxyNotEnoughFee + ) +) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt index 624415e608..ba2d8ea218 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt @@ -43,6 +43,7 @@ import io.novafoundation.nova.feature_account_impl.presentation.watchOnly.change import io.novafoundation.nova.feature_account_impl.presentation.watchOnly.create.di.CreateWatchWalletComponent import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi +import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi import io.novafoundation.nova.runtime.di.RuntimeApi import io.novafoundation.nova.web3names.di.Web3NamesApi @@ -128,6 +129,7 @@ interface AccountFeatureComponent : AccountFeatureApi { CommonApi::class, RuntimeApi::class, CurrencyFeatureApi::class, + WalletFeatureApi::class, DbApi::class, VersionsFeatureApi::class, Web3NamesApi::class diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt index a9a575ff6d..e8a93a3d47 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt @@ -33,6 +33,8 @@ import io.novafoundation.nova.core_db.dao.NodeDao import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor import io.novafoundation.nova.feature_currency_api.domain.interfaces.CurrencyRepository import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry +import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory @@ -117,6 +119,10 @@ interface AccountFeatureDependencies { fun computationalCache(): ComputationalCache + fun walletRepository(): WalletRepository + + fun assetSourceRegistry(): AssetSourceRegistry + val systemCallExecutor: SystemCallExecutor val multiChainQrSharingFactory: MultiChainQrSharingFactory diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureHolder.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureHolder.kt index 7037bd70ad..b0aa375980 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureHolder.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureHolder.kt @@ -12,6 +12,7 @@ import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.Polk import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi +import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi import io.novafoundation.nova.runtime.di.RuntimeApi import io.novafoundation.nova.web3names.di.Web3NamesApi import javax.inject.Inject @@ -33,6 +34,7 @@ class AccountFeatureHolder @Inject constructor( .dbApi(getFeature(DbApi::class.java)) .runtimeApi(getFeature(RuntimeApi::class.java)) .currencyFeatureApi(getFeature(CurrencyFeatureApi::class.java)) + .walletFeatureApi(getFeature(WalletFeatureApi::class.java)) .versionsFeatureApi(getFeature(VersionsFeatureApi::class.java)) .web3NamesApi(getFeature(Web3NamesApi::class.java)) .build() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index dc447616ba..36feb997f8 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -65,7 +65,7 @@ import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFa import io.novafoundation.nova.feature_account_impl.di.modules.AdvancedEncryptionStoreModule import io.novafoundation.nova.feature_account_impl.di.modules.IdentityProviderModule import io.novafoundation.nova.feature_account_impl.di.modules.ParitySignerModule -import io.novafoundation.nova.feature_account_impl.di.modules.SignersModule +import io.novafoundation.nova.feature_account_impl.di.modules.signers.SignersModule import io.novafoundation.nova.feature_account_impl.di.modules.WatchOnlyModule import io.novafoundation.nova.feature_account_impl.domain.AccountInteractorImpl import io.novafoundation.nova.feature_account_impl.domain.MetaAccountGroupingInteractorImpl diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt new file mode 100644 index 0000000000..88223c2cd6 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt @@ -0,0 +1,73 @@ +package io.novafoundation.nova.feature_account_impl.di.modules.signers + +import dagger.Module +import dagger.Provides +import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService +import io.novafoundation.nova.common.utils.DefaultMutableSharedState +import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository +import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider +import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter +import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter +import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator +import io.novafoundation.nova.feature_account_impl.data.signer.RealSignerProvider +import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner +import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.ParitySignerSigner +import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultSigner +import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeSignerFactory +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationSystem +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxyHaveEnoughFeeValidationFactory +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.proxiedExtrinsicValidationSystem +import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory +import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySigner +import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry +import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic + +@Module +class ProxiedSignerModule { + + @Provides + @FeatureScope + fun provideProxyHaveEnoughFeeValidationFactory( + assetSourceRegistry: AssetSourceRegistry, + accountRepository: AccountRepository, + walletRepository: WalletRepository, + extrinsicService: ExtrinsicService + ) = ProxyHaveEnoughFeeValidationFactory( + assetSourceRegistry, + accountRepository, + walletRepository, + extrinsicService + ) + + @Provides + @FeatureScope + fun provideProxiedExtrinsicValidationSystem( + proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory + ): ProxiedExtrinsicValidationSystem { + return proxiedExtrinsicValidationSystem(proxyHaveEnoughFeeValidationFactory) + } + + @Provides + @FeatureScope + fun provideProxiedSignerFactory( + secretStoreV2: SecretStoreV2, + chainRegistry: ChainRegistry, + accountRepository: AccountRepository, + proxySigningPresenter: ProxySigningPresenter, + proxyRepository: ProxyRepository, + proxiedExtrinsicValidationSystem: ProxiedExtrinsicValidationSystem, + ) = ProxiedSignerFactory(secretStoreV2, chainRegistry, accountRepository, proxySigningPresenter, proxyRepository, proxiedExtrinsicValidationSystem) + +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt similarity index 92% rename from feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt rename to feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt index ca5106c618..882e312457 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/SignersModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_account_impl.di.modules +package io.novafoundation.nova.feature_account_impl.di.modules.signers import dagger.Module import dagger.Provides @@ -28,7 +28,7 @@ import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notS import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic -@Module +@Module(includes = [ProxiedSignerModule::class]) class SignersModule { @Provides @@ -43,16 +43,6 @@ class SignersModule { twoFactorVerificationService: TwoFactorVerificationService ) = SecretsSignerFactory(secretStoreV2, chainRegistry, twoFactorVerificationService) - @Provides - @FeatureScope - fun provideProxiedSignerFactory( - secretStoreV2: SecretStoreV2, - chainRegistry: ChainRegistry, - accountRepository: AccountRepository, - proxySigningPresenter: ProxySigningPresenter, - proxyRepository: ProxyRepository, - ) = ProxiedSignerFactory(secretStoreV2, chainRegistry, accountRepository, proxySigningPresenter, proxyRepository) - @Provides @FeatureScope fun provideProxiedFeeSignerFactory( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt index dab826b822..71518ff958 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt @@ -36,7 +36,7 @@ class ProxyFormatter( fun mapProxyTypeToString(resourceManager: ResourceManager, type: ProxyAccount.ProxyType): String { return when (type) { ProxyAccount.ProxyType.Any -> resourceManager.getString(R.string.account_proxy_type_any) - ProxyAccount.ProxyType.NonTransfer -> resourceManager.getString(R.string.account_proxy_type_any) + ProxyAccount.ProxyType.NonTransfer -> resourceManager.getString(R.string.account_proxy_type_non_transfer) ProxyAccount.ProxyType.Governance -> resourceManager.getString(R.string.account_proxy_type_governance) ProxyAccount.ProxyType.Staking -> resourceManager.getString(R.string.account_proxy_type_staking) ProxyAccount.ProxyType.IdentityJudgement -> resourceManager.getString(R.string.account_proxy_type_identity_judgement) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt index f8061b4585..7fb03d3190 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt @@ -4,14 +4,23 @@ import android.text.SpannableStringBuilder import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.amountFromPlanks import io.novafoundation.nova.common.utils.colorSpan +import io.novafoundation.nova.common.utils.formatting.format import io.novafoundation.nova.common.utils.formatting.spannable.SpannableFormatter import io.novafoundation.nova.common.utils.toSpannable +import io.novafoundation.nova.common.view.dialog.dialog +import io.novafoundation.nova.common.view.dialog.errorDialog +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable +import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks +import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import java.math.BigInteger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.coroutines.resume @@ -68,6 +77,35 @@ class RealProxySigningPresenter( ) } + override suspend fun notEnoughFee(proxyMetaAccount: MetaAccount, asset: Chain.Asset, availableBalanceToPayFee: BigInteger, fee: Fee) { + suspendCoroutine { continuation -> + dialog(contextManager.getApplicationContext()) { + setTitle(R.string.error_not_enough_to_pay_fee_title) + setMessage( + resourceManager.getString( + R.string.proxy_error_not_enough_to_pay_fee_message, + proxyMetaAccount.name, + asset.amountFromPlanks(fee.amount).formatTokenAmount(asset), + asset.amountFromPlanks(availableBalanceToPayFee).formatTokenAmount(asset), + ) + ) + + setPositiveButton(io.novafoundation.nova.common.R.string.common_close, null) + setOnDismissListener { continuation.resume(Unit) } + } + } + } + + override suspend fun unsupportedValidationError() { + suspendCoroutine { continuation -> + dialog(contextManager.getApplicationContext()) { + setTitle(R.string.common_unknown_error) + setPositiveButton(io.novafoundation.nova.common.R.string.common_close, null) + setOnDismissListener { continuation.resume(Unit) } + } + } + } + private fun noNeedToShowWarning(proxyMetaAccount: MetaAccount): Boolean { return preferences.getBoolean(makePrefsKey(proxyMetaAccount), false) } From 25854f392bc5746b74dff00ab6bac1d7dc517d1a Mon Sep 17 00:00:00 2001 From: Valentun Date: Wed, 20 Dec 2023 10:30:32 +0300 Subject: [PATCH 052/100] Code style --- .../data/ethereum/transaction/RealEvmTransactionService.kt | 2 -- .../runtime/storage/source/query/BaseStorageQueryContext.kt | 1 - 2 files changed, 3 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index b1f6fc6efc..28a556bd9e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -23,8 +23,6 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow -import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw import org.web3j.crypto.RawTransaction diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt index 9e1c2ab84e..5be9f7d720 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt @@ -7,7 +7,6 @@ import io.novafoundation.nova.common.data.network.runtime.binding.fromHexOrIncom import io.novafoundation.nova.common.data.network.runtime.binding.incompatible import io.novafoundation.nova.common.utils.ComponentHolder import io.novafoundation.nova.common.utils.mapValuesNotNull -import io.novafoundation.nova.common.utils.splitKeyToComponents import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilderImpl From f84c1b3beb5c2e8ce7a0305600488542a3369023 Mon Sep 17 00:00:00 2001 From: Valentun Date: Wed, 20 Dec 2023 14:07:58 +0300 Subject: [PATCH 053/100] Refactor extrinsic service --- .../nova/balances/BalancesIntegrationTest.kt | 3 +- .../nova/core_db/dao/MetaAccountDao.kt | 32 ++-- .../transaction/EvmTransactionService.kt | 3 +- .../ethereum/transaction/TransactionOrigin.kt | 7 + .../data/extrinsic/ExtrinsicService.kt | 69 +++++---- .../data/extrinsic/ExtrinsicServiceExt.kt | 10 +- .../domain/interfaces/AccountRepository.kt | 7 +- .../domain/interfaces/AccountRepositoryExt.kt | 8 +- .../account/AddressDisplayUseCase.kt | 13 +- .../account/icon/AddressIconGeneratorExt.kt | 2 +- .../transaction/RealEvmTransactionService.kt | 14 +- .../data/extrinsic/RealExtrinsicService.kt | 138 ++++++++---------- .../data/repository/AccountRepositoryImpl.kt | 12 +- .../datasource/AccountDataSource.kt | 8 +- .../datasource/AccountDataSourceImpl.kt | 13 +- .../account/identity/LocalIdentityProvider.kt | 2 +- .../domain/send/SendInteractor.kt | 4 +- .../CrowdloanContributeInteractor.kt | 14 +- .../moonbeam/MoonbeamCrowdloanInteractor.kt | 10 +- .../terms/MoonbeamCrowdloanTermsViewModel.kt | 4 +- .../select/CrowdloanContributeViewModel.kt | 3 +- .../signExtrinsic/ExternaSignViewModel.kt | 2 +- .../removeVotes/RemoveTrackVotesInteractor.kt | 4 +- .../vote/VoteReferendumInteractor.kt | 6 +- ...RealNewDelegationChooseAmountInteractor.kt | 13 +- .../RealRemoveTrackVotesInteractor.kt | 19 +-- .../revoke/RevokeDelegationsInteractor.kt | 2 +- .../unlock/GovernanceUnlockInteractor.kt | 23 +-- .../vote/RealVoteReferendumInteractor.kt | 11 +- .../NewDelegationChooseAmountViewModel.kt | 4 +- .../RevokeDelegationConfirmViewModel.kt | 2 +- .../domain/model/relaychain/StakingState.kt | 6 + .../domain/bagList/rebag/RebagInteractor.kt | 16 +- .../NominationPoolsBondMoreInteractor.kt | 15 +- .../NominationPoolsClaimRewardsInteractor.kt | 15 +- .../redeem/NominationPoolsRedeemInteractor.kt | 11 +- .../unbond/NominationPoolsUnbondInteractor.kt | 15 +- .../ParachainStakingRebondInteractor.kt | 14 +- .../ParachainStakingRedeemInteractor.kt | 18 ++- .../start/StartParachainStakingInteractor.kt | 24 +-- .../ParachainStakingUnbondInteractor.kt | 14 +- .../yieldBoost/YieldBoostInteractor.kt | 15 +- .../domain/payout/PayoutInteractor.kt | 2 +- .../setup/ChangeValidatorsInteractor.kt | 17 ++- .../domain/staking/bond/BondMoreInteractor.kt | 15 +- .../controller/ControllerInteractor.kt | 14 +- .../domain/staking/rebond/RebondInteractor.kt | 11 +- .../domain/staking/redeem/RedeemInteractor.kt | 8 +- .../ChangeRewardDestinationInteractor.kt | 12 +- .../common/StartMultiStakingInteractor.kt | 9 +- .../domain/staking/unbond/UnbondInteractor.kt | 11 +- .../validations/AccountRequiredValidation.kt | 2 +- .../payouts/confirm/ConfirmPayoutViewModel.kt | 2 +- .../bond/select/SelectBondMoreViewModel.kt | 2 +- .../controller/set/SetControllerViewModel.kt | 2 +- .../rebond/confirm/ConfirmRebondViewModel.kt | 2 +- .../rebond/custom/CustomRebondViewModel.kt | 4 +- .../SetupAmountMultiStakingViewModel.kt | 4 +- .../unbond/select/SelectUnbondViewModel.kt | 2 - .../ConfirmChangeValidatorsViewModel.kt | 4 +- .../AssetConversionExchange.kt | 16 +- .../SwapTransactionHistoryRepository.kt | 2 +- .../data/cache/AssetCache.kt | 2 +- .../assets/tranfers/AssetTransfers.kt | 5 +- .../crosschain/CrossChainTransactor.kt | 3 +- .../presentation/mixin/fee/FeeLoaderMixin.kt | 58 +------- .../mixin/fee/GenericFeeLoaderProvider.kt | 15 -- .../assets/transfers/BaseAssetTransfers.kt | 12 +- .../transfers/UnsupportedAssetTransfers.kt | 3 +- .../evmErc20/EvmErc20AssetTransfers.kt | 3 +- .../evmNative/EvmNativeAssetTransfers.kt | 3 +- .../crosschain/RealCrossChainTransactor.kt | 8 +- .../crosschain/RealCrossChainWeigher.kt | 3 +- .../data/repository/WalletRepositoryImpl.kt | 4 +- 74 files changed, 446 insertions(+), 439 deletions(-) diff --git a/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt index 1d250ffa17..08037c0e77 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt @@ -9,6 +9,7 @@ import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.utils.fromJson import io.novafoundation.nova.common.utils.hasModule import io.novafoundation.nova.common.utils.system +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent import io.novafoundation.nova.runtime.BuildConfig.TEST_CHAINS_URL @@ -124,7 +125,7 @@ class BalancesIntegrationTest( private suspend fun testFeeLoadingAsync(chain: Chain) { return coroutineScope { withTimeout(80.seconds) { - extrinsicService.estimateFee(chain) { + extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { systemRemark(byteArrayOf(0)) val haveBatch = runtime.metadata.hasModule("Utility") diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 7f86c3ada6..1add2cec14 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -18,32 +18,30 @@ import java.math.BigDecimal import java.math.BigInteger /** - * Fetch meta account where - * accountId = meta.substrateAccountId - * or hex(accountId) = meta.ethereumAddress - * or there is a child chain account which have child.accountId = accountId + * Fetch meta account where either + * 1. chain account for specified chain is present and its accountId matches + * 2. chain account for specified is missing but one of base accountIds matches + * + * Note that if both chain account and base accounts are present than we should filter out entries where chain account matches but base accounts does not */ @Language("RoomSql") private const val FIND_BY_ADDRESS_WHERE_CLAUSE = """ - WHERE substrateAccountId = :accountId - OR ethereumAddress = :accountId - OR id = ( - SELECT id FROM meta_accounts AS m - INNER JOIN chain_accounts as c ON m.id = c.metaId - WHERE c.accountId = :accountId - ) - ORDER BY (CASE WHEN isSelected THEN 0 ELSE 1 END) + LEFT JOIN chain_accounts as c ON m.id = c.metaId + WHERE + (c.chainId = :chainId AND c.accountId IS NOT NULL AND c.accountId = :accountId) + OR (c.accountId IS NULL AND (substrateAccountId = :accountId OR ethereumAddress = :accountId)) + ORDER BY (CASE WHEN isSelected THEN 0 ELSE 1 END) """ @Language("RoomSql") private const val FIND_ACCOUNT_BY_ADDRESS_QUERY = """ - SELECT * FROM meta_accounts + SELECT * FROM meta_accounts as m $FIND_BY_ADDRESS_WHERE_CLAUSE """ @Language("RoomSql") private const val FIND_NAME_BY_ADDRESS_QUERY = """ - SELECT name FROM meta_accounts + SELECT name FROM meta_accounts as m $FIND_BY_ADDRESS_WHERE_CLAUSE """ @@ -150,14 +148,14 @@ interface MetaAccountDao { fun metaAccountInfoFlow(metaId: Long): Flow @Query("SELECT EXISTS ($FIND_ACCOUNT_BY_ADDRESS_QUERY)") - fun isMetaAccountExists(accountId: AccountId): Boolean + fun isMetaAccountExists(accountId: AccountId, chainId: String): Boolean @Query(FIND_ACCOUNT_BY_ADDRESS_QUERY) @Transaction - fun getMetaAccountInfo(accountId: AccountId): RelationJoinedMetaAccountInfo? + fun getMetaAccountInfo(accountId: AccountId, chainId: String): RelationJoinedMetaAccountInfo? @Query(FIND_NAME_BY_ADDRESS_QUERY) - fun metaAccountNameFor(accountId: AccountId): String? + fun metaAccountNameFor(accountId: AccountId, chainId: String): String? @Query("UPDATE meta_accounts SET name = :newName WHERE id = :metaId") suspend fun updateName(metaId: Long, newName: String) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/EvmTransactionService.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/EvmTransactionService.kt index 5186b21890..84f9be5fc1 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/EvmTransactionService.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/EvmTransactionService.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_account_api.data.ethereum.transaction +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.runtime.ethereum.transaction.builder.EvmTransactionBuilder import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId @@ -23,5 +24,5 @@ interface EvmTransactionService { origin: TransactionOrigin = TransactionOrigin.SelectedWallet, fallbackGasLimit: BigInteger = DefaultGasProvider.GAS_LIMIT, building: EvmTransactionBuilding, - ): Result + ): Result } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt index 0396e87200..1c6f5040a0 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_account_api.data.ethereum.transaction +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import jp.co.soramitsu.fearless_utils.runtime.AccountId sealed class TransactionOrigin { @@ -7,4 +8,10 @@ sealed class TransactionOrigin { object SelectedWallet : TransactionOrigin() class WalletWithAccount(val accountId: AccountId) : TransactionOrigin() + + class Wallet(val metaAccount: MetaAccount): TransactionOrigin() } + +fun AccountId.intoOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(this) + +fun MetaAccount.intoOrigin(): TransactionOrigin = TransactionOrigin.Wallet(this) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt index cb5ce4b165..431c7bd9ed 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt @@ -9,73 +9,72 @@ import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer import kotlinx.coroutines.flow.Flow -import java.math.BigInteger -typealias FormExtrinsicWithOrigin = suspend ExtrinsicBuilder.(origin: AccountId) -> Unit +typealias FormExtrinsicWithOrigin = suspend ExtrinsicBuilder.(origin: SubmissionOrigin) -> Unit -typealias FormMultiExtrinsicWithOrigin = suspend CallBuilder.(origin: AccountId) -> Unit +typealias FormMultiExtrinsicWithOrigin = suspend CallBuilder.(origin: SubmissionOrigin) -> Unit typealias FormMultiExtrinsic = suspend CallBuilder.() -> Unit typealias ExtrinsicHash = String -class ExtrinsicSubmission(val hash: String, val origin: AccountId) +class SubmissionOrigin( + /** + * Origin that was originally requested to sign the transaction + */ + val requestedOrigin: AccountId, -interface ExtrinsicService { + /** + * Origin that was actually used to sign the transaction. + * It might differ from [requestedOrigin] if [Signer] modified the origin, for example in the case of Proxied wallet + */ + val actualOrigin: AccountId +) { - suspend fun submitMultiExtrinsicAwaitingInclusion( - chain: Chain, - origin: TransactionOrigin, - formExtrinsic: FormMultiExtrinsicWithOrigin, - ): RetriableMultiResult + companion object { - @Suppress("DeprecatedCallableAddReplaceWith") - @Deprecated("Use submitExtrinsicWithSelectedWalletV2 instead") - suspend fun submitExtrinsicWithSelectedWallet( - chain: Chain, - formExtrinsic: FormExtrinsicWithOrigin, - ): Result = submitExtrinsicWithSelectedWalletV2(chain, formExtrinsic) - .map { it.hash } + fun singleOrigin(origin: AccountId) = SubmissionOrigin(origin, origin) + } +} - suspend fun submitExtrinsicWithSelectedWalletV2( - chain: Chain, - formExtrinsic: FormExtrinsicWithOrigin, - ): Result +class ExtrinsicSubmission(val hash: String, val submissionOrigin: SubmissionOrigin) + +interface ExtrinsicService { - suspend fun submitAndWatchExtrinsicWithSelectedWallet( + suspend fun submitExtrinsic( chain: Chain, + origin: TransactionOrigin, formExtrinsic: FormExtrinsicWithOrigin, - ): Flow + ): Result - suspend fun submitExtrinsicWithAnySuitableWallet( + suspend fun submitAndWatchExtrinsic( chain: Chain, - accountId: ByteArray, + origin: TransactionOrigin, formExtrinsic: FormExtrinsicWithOrigin, - ): Result + ): Result> - suspend fun submitAndWatchExtrinsicAnySuitableWallet( + suspend fun submitMultiExtrinsicAwaitingInclusion( chain: Chain, - accountId: ByteArray, - formExtrinsic: FormExtrinsicWithOrigin, - ): Flow + origin: TransactionOrigin, + formExtrinsic: FormMultiExtrinsicWithOrigin, + ): RetriableMultiResult suspend fun paymentInfo( chain: Chain, + origin: TransactionOrigin, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, ): FeeResponse suspend fun estimateFee( chain: Chain, - formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, - ): BigInteger - - suspend fun estimateFeeV2( - chain: Chain, + origin: TransactionOrigin, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, ): Fee suspend fun estimateMultiFee( chain: Chain, + origin: TransactionOrigin, formExtrinsic: FormMultiExtrinsic, ): Fee diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt index fb23680fc6..44a8bfa259 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt @@ -2,14 +2,10 @@ package io.novafoundation.nova.feature_account_api.data.extrinsic import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first -suspend fun ExtrinsicService.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion( - chain: Chain, - formExtrinsic: FormExtrinsicWithOrigin, -): Result = runCatching { - submitAndWatchExtrinsicWithSelectedWallet(chain, formExtrinsic) - .filterIsInstance() - .first() +suspend fun Result>.awaitInBlock(): Result = map { + it.filterIsInstance().first() } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index 795e064fdb..4d9317135d 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -9,6 +9,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.Mnemonic import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.flow.Flow @@ -37,9 +38,9 @@ interface AccountRepository { fun selectedMetaAccountFlow(): Flow - suspend fun findMetaAccount(accountId: ByteArray): MetaAccount? + suspend fun findMetaAccount(accountId: ByteArray, chainId: ChainId): MetaAccount? - suspend fun accountNameFor(accountId: AccountId): String? + suspend fun accountNameFor(accountId: AccountId, chainId: ChainId): String? suspend fun allMetaAccounts(): List @@ -121,5 +122,5 @@ interface AccountRepository { password: String ): String - suspend fun isAccountExists(accountId: AccountId): Boolean + suspend fun isAccountExists(accountId: AccountId, chainId: ChainId): Boolean } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepositoryExt.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepositoryExt.kt index 2413766312..a9c3196f6e 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepositoryExt.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepositoryExt.kt @@ -5,10 +5,11 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.AccountId -suspend fun AccountRepository.findMetaAccountOrThrow(accountId: AccountId) = findMetaAccount(accountId) +suspend fun AccountRepository.findMetaAccountOrThrow(accountId: AccountId, chainId: ChainId) = findMetaAccount(accountId, chainId) ?: error("No meta account found for accountId: ${accountId.toHexString()}") suspend fun AccountRepository.requireIdOfSelectedMetaAccountIn(chain: Chain): AccountId { @@ -23,9 +24,10 @@ suspend fun AccountRepository.getIdOfSelectedMetaAccountIn(chain: Chain): Accoun return metaAccount.accountIdIn(chain) } -suspend fun AccountRepository.requireMetaAccountFor(transactionOrigin: TransactionOrigin): MetaAccount { +suspend fun AccountRepository.requireMetaAccountFor(transactionOrigin: TransactionOrigin, chainId: ChainId): MetaAccount { return when (transactionOrigin) { TransactionOrigin.SelectedWallet -> getSelectedMetaAccount() - is TransactionOrigin.WalletWithAccount -> findMetaAccountOrThrow(transactionOrigin.accountId) + is TransactionOrigin.WalletWithAccount -> findMetaAccountOrThrow(transactionOrigin.accountId, chainId) + is TransactionOrigin.Wallet -> transactionOrigin.metaAccount } } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/AddressDisplayUseCase.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/AddressDisplayUseCase.kt index 0b3fa03602..6d87f876db 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/AddressDisplayUseCase.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/AddressDisplayUseCase.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_account_api.presenatation.account import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -18,14 +19,8 @@ class AddressDisplayUseCase( } } - @Deprecated("Does not work with Meta Accounts. Use `invoke(chain: Chain, address: String)` instead") - // TODO remove - suspend operator fun invoke(address: String): String? = withContext(Dispatchers.Default) { - accountRepository.getAccountOrNull(address)?.name - } - - suspend operator fun invoke(accountId: AccountId): String? = withContext(Dispatchers.Default) { - accountRepository.findMetaAccount(accountId)?.name + suspend operator fun invoke(accountId: AccountId, chainId: ChainId): String? = withContext(Dispatchers.Default) { + accountRepository.findMetaAccount(accountId, chainId)?.name } suspend fun createIdentifier(): Identifier = withContext(Dispatchers.Default) { @@ -39,5 +34,5 @@ class AddressDisplayUseCase( } suspend operator fun AddressDisplayUseCase.invoke(chain: Chain, address: String): String? { - return runCatching { invoke(chain.accountIdOf(address)) }.getOrNull() + return runCatching { invoke(chain.accountIdOf(address), chain.id) }.getOrNull() } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/icon/AddressIconGeneratorExt.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/icon/AddressIconGeneratorExt.kt index 289c6ff373..76bd92e651 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/icon/AddressIconGeneratorExt.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/icon/AddressIconGeneratorExt.kt @@ -126,7 +126,7 @@ suspend fun AddressIconGenerator.createAccountAddressModel( chain: Chain, accountId: AccountId, addressDisplayUseCase: AddressDisplayUseCase, -) = createAccountAddressModel(chain, accountId, addressDisplayUseCase.invoke(accountId)) +) = createAccountAddressModel(chain, accountId, addressDisplayUseCase.invoke(accountId, chain.id)) suspend fun AddressIconGenerator.createIdentityAddressModel( chain: Chain, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index 28a556bd9e..2fe622a8fc 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -5,8 +5,9 @@ import io.novafoundation.nova.common.utils.toEcdsaSignatureData import io.novafoundation.nova.core.ethereum.Web3Api import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionBuilding import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService -import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionHash import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.extrinsic.SubmissionOrigin import io.novafoundation.nova.feature_account_api.data.model.EvmFee import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider @@ -50,7 +51,7 @@ internal class RealEvmTransactionService( val web3Api = chainRegistry.getCallEthereumApiOrThrow(chainId) val chain = chainRegistry.getChain(chainId) - val submittingMetaAccount = accountRepository.requireMetaAccountFor(origin) + val submittingMetaAccount = accountRepository.requireMetaAccountFor(origin, chainId) val submittingAddress = submittingMetaAccount.requireAddressIn(chain) val txBuilder = EvmTransactionBuilder().apply(building) @@ -68,10 +69,11 @@ internal class RealEvmTransactionService( origin: TransactionOrigin, fallbackGasLimit: BigInteger, building: EvmTransactionBuilding - ): Result = runCatching { + ): Result = runCatching { val chain = chainRegistry.getChain(chainId) - val submittingMetaAccount = accountRepository.requireMetaAccountFor(origin) + val submittingMetaAccount = accountRepository.requireMetaAccountFor(origin, chainId) val submittingAddress = submittingMetaAccount.requireAddressIn(chain) + val submittingAccountId = submittingMetaAccount.requireAccountIdIn(chain) val web3Api = chainRegistry.getCallEthereumApiOrThrow(chainId) val txBuilder = EvmTransactionBuilder().apply(building) @@ -89,7 +91,9 @@ internal class RealEvmTransactionService( val txForSign = txBuilder.buildForSign(nonce = nonce, gasPrice = evmFee.gasPrice, gasLimit = evmFee.gasLimit) val toSubmit = signTransaction(txForSign, submittingMetaAccount, chain) - web3Api.sendTransaction(toSubmit) + val txHash = web3Api.sendTransaction(toSubmit) + + ExtrinsicSubmission(hash = txHash, submissionOrigin = SubmissionOrigin.singleOrigin(submittingAccountId)) } private suspend fun signTransaction(txForSign: RawTransaction, metaAccount: MetaAccount, chain: Chain): String { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index 8c2b757080..ccd8d9ee83 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -13,12 +13,13 @@ import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmis import io.novafoundation.nova.feature_account_api.data.extrinsic.FormExtrinsicWithOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.FormMultiExtrinsic import io.novafoundation.nova.feature_account_api.data.extrinsic.FormMultiExtrinsicWithOrigin +import io.novafoundation.nova.feature_account_api.data.extrinsic.SubmissionOrigin import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.data.model.InlineFee import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.requireMetaAccountFor -import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus @@ -29,7 +30,6 @@ 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.novafoundation.nova.runtime.network.rpc.RpcCalls -import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.definitions.types.fromHex import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.Extrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder @@ -37,7 +37,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first -import java.math.BigInteger + class RealExtrinsicService( private val rpcCalls: RpcCalls, @@ -48,13 +48,25 @@ class RealExtrinsicService( private val extrinsicSplitter: ExtrinsicSplitter, ) : ExtrinsicService { + override suspend fun submitExtrinsic( + chain: Chain, + origin: TransactionOrigin, + formExtrinsic: FormExtrinsicWithOrigin + ): Result = runCatching { + val metaAccount = accountRepository.requireMetaAccountFor(origin, chain.id) + val (extrinsic, submissionOrigin) = buildExtrinsic(chain, metaAccount, formExtrinsic) + val hash = rpcCalls.submitExtrinsic(chain.id, extrinsic) + + ExtrinsicSubmission(hash, submissionOrigin) + } + override suspend fun submitMultiExtrinsicAwaitingInclusion( chain: Chain, origin: TransactionOrigin, formExtrinsic: FormMultiExtrinsicWithOrigin ): RetriableMultiResult { return runMultiCatching( - intermediateListLoading = { constructSplitExtrinsicsForSubmission(chain, formExtrinsic, origin) }, + intermediateListLoading = { constructSplitExtrinsicsForSubmission(chain, origin, formExtrinsic) }, listProcessing = { extrinsic -> rpcCalls.submitAndWatchExtrinsic(chain.id, extrinsic) .filterIsInstance() @@ -63,59 +75,24 @@ class RealExtrinsicService( ) } - override suspend fun submitExtrinsicWithSelectedWalletV2( - chain: Chain, - formExtrinsic: FormExtrinsicWithOrigin, - ): Result { - val account = accountRepository.getSelectedMetaAccount() - val accountId = account.requireAccountIdIn(chain) - - return submitExtrinsicWithAnySuitableWalletV2(chain, accountId, formExtrinsic) - } - - override suspend fun submitAndWatchExtrinsicWithSelectedWallet( - chain: Chain, - formExtrinsic: FormExtrinsicWithOrigin, - ): Flow { - val account = accountRepository.getSelectedMetaAccount() - val accountId = account.accountIdIn(chain)!! - - return submitAndWatchExtrinsicAnySuitableWallet(chain, accountId, formExtrinsic) - } - - override suspend fun submitExtrinsicWithAnySuitableWallet( - chain: Chain, - accountId: ByteArray, - formExtrinsic: FormExtrinsicWithOrigin, - ): Result = submitExtrinsicWithAnySuitableWalletV2(chain, accountId, formExtrinsic) - .map { it.hash } - - private suspend fun submitExtrinsicWithAnySuitableWalletV2( - chain: Chain, - accountId: ByteArray, - formExtrinsic: FormExtrinsicWithOrigin, - ): Result = runCatching { - val extrinsic = buildExtrinsic(chain, accountId, formExtrinsic) - val hash = rpcCalls.submitExtrinsic(chain.id, extrinsic) - ExtrinsicSubmission(hash, accountId) - } - - override suspend fun submitAndWatchExtrinsicAnySuitableWallet( + override suspend fun submitAndWatchExtrinsic( chain: Chain, - accountId: ByteArray, - formExtrinsic: FormExtrinsicWithOrigin, - ): Flow { - val extrinsic = buildExtrinsic(chain, accountId, formExtrinsic) + origin: TransactionOrigin, + formExtrinsic: FormExtrinsicWithOrigin + ): Result> = runCatching { + val metaAccount = accountRepository.requireMetaAccountFor(origin, chain.id) + val (extrinsic) = buildExtrinsic(chain, metaAccount, formExtrinsic) - return rpcCalls.submitAndWatchExtrinsic(chain.id, extrinsic) + rpcCalls.submitAndWatchExtrinsic(chain.id, extrinsic) .takeWhileInclusive { !it.terminal } } override suspend fun paymentInfo( chain: Chain, + origin: TransactionOrigin, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, ): FeeResponse { - val extrinsic = extrinsicBuilderFactory.createForFee(getFeeSigner(chain), chain) + val extrinsic = extrinsicBuilderFactory.createForFee(getFeeSigner(chain, origin), chain) .also { it.formExtrinsic() } .build() @@ -124,17 +101,14 @@ class RealExtrinsicService( override suspend fun estimateFee( chain: Chain, - formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, - ): BigInteger { - val extrinsicBuilder = extrinsicBuilderFactory.createForFee(getFeeSigner(chain), chain) + origin: TransactionOrigin, + formExtrinsic: suspend ExtrinsicBuilder.() -> Unit + ): Fee { + val extrinsicBuilder = extrinsicBuilderFactory.createForFee(getFeeSigner(chain, origin), chain) extrinsicBuilder.formExtrinsic() val extrinsic = extrinsicBuilder.build() - return estimateFee(chain, extrinsic).amount - } - - override suspend fun estimateFeeV2(chain: Chain, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit): Fee { - return InlineFee(estimateFee(chain, formExtrinsic)) + return estimateFee(chain, extrinsic) } override suspend fun estimateFee(chain: Chain, extrinsic: String): Fee { @@ -150,10 +124,14 @@ class RealExtrinsicService( return InlineFee(tip + baseFee) } - override suspend fun estimateMultiFee(chain: Chain, formExtrinsic: FormMultiExtrinsic): Fee { - val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(getFeeSigner(chain), chain) + override suspend fun estimateMultiFee( + chain: Chain, + origin: TransactionOrigin, + formExtrinsic: FormMultiExtrinsic + ): Fee { + val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(getFeeSigner(chain, origin), chain) - val extrinsics = constructSplitExtrinsics(chain, formExtrinsic, feeExtrinsicBuilderSequence) + val extrinsics = constructSplitExtrinsics(chain, origin, formExtrinsic, feeExtrinsicBuilderSequence) val separateFees = extrinsics.map { estimateFee(chain, it).amount } val totalFee = separateFees.sum() @@ -163,29 +141,33 @@ class RealExtrinsicService( private suspend fun constructSplitExtrinsicsForSubmission( chain: Chain, - formExtrinsic: FormMultiExtrinsicWithOrigin, origin: TransactionOrigin, + formExtrinsic: FormMultiExtrinsicWithOrigin, ): List { - val metaAccount = accountRepository.requireMetaAccountFor(origin) + val metaAccount = accountRepository.requireMetaAccountFor(origin, chain.id) val signer = signerProvider.signerFor(metaAccount) - val accountId = metaAccount.requireAccountIdIn(chain) - val extrinsicBuilderSequence = extrinsicBuilderFactory.createMulti(chain, signer, accountId) + val requestedOrigin = metaAccount.requireAccountIdIn(chain) + val actualOrigin = signer.signerAccountId(chain) + val submissionOrigin = SubmissionOrigin(requestedOrigin = requestedOrigin, actualOrigin = actualOrigin) + + val extrinsicBuilderSequence = extrinsicBuilderFactory.createMulti(chain, signer, requestedOrigin) - val formExtrinsicWithOrigin: FormMultiExtrinsic = { formExtrinsic(accountId) } + val formExtrinsicWithOrigin: FormMultiExtrinsic = { formExtrinsic(submissionOrigin) } - return constructSplitExtrinsics(chain, formExtrinsicWithOrigin, extrinsicBuilderSequence) + return constructSplitExtrinsics(chain, origin, formExtrinsicWithOrigin, extrinsicBuilderSequence) } private suspend fun constructSplitExtrinsics( chain: Chain, + origin: TransactionOrigin, formExtrinsic: FormMultiExtrinsic, extrinsicBuilderSequence: Sequence, ): List = coroutineScope { val runtime = chainRegistry.getRuntime(chain.id) val callBuilder = SimpleCallBuilder(runtime).apply { formExtrinsic() } - val splitCalls = extrinsicSplitter.split(getFeeSigner(chain), callBuilder, chain) + val splitCalls = extrinsicSplitter.split(getFeeSigner(chain, origin), callBuilder, chain) val extrinsicBuilderIterator = extrinsicBuilderSequence.iterator() @@ -202,21 +184,29 @@ class RealExtrinsicService( private suspend fun buildExtrinsic( chain: Chain, - selectedAccountId: ByteArray, + metaAccount: MetaAccount, formExtrinsic: FormExtrinsicWithOrigin, - ): String { - val metaAccount = accountRepository.findMetaAccount(selectedAccountId) ?: error("No meta account found accessing ${selectedAccountId.toHexString()}") + ): SubmissionRaw { val signer = signerProvider.signerFor(metaAccount) - val extrinsicBuilder = extrinsicBuilderFactory.create(chain, signer, selectedAccountId) + val requestedOrigin = metaAccount.requireAccountIdIn(chain) + val actualOrigin = signer.signerAccountId(chain) + + val submissionOrigin = SubmissionOrigin(requestedOrigin = requestedOrigin, actualOrigin = actualOrigin) + + val extrinsicBuilder = extrinsicBuilderFactory.create(chain, signer, requestedOrigin) - extrinsicBuilder.formExtrinsic(selectedAccountId) + extrinsicBuilder.formExtrinsic(submissionOrigin) - return extrinsicBuilder.build(useBatchAll = true) + val extrinsic = extrinsicBuilder.build(useBatchAll = true) + + return SubmissionRaw(extrinsic, submissionOrigin) } - private suspend fun getFeeSigner(chain: Chain): NovaSigner { - val metaAccount = accountRepository.getSelectedMetaAccount() + private suspend fun getFeeSigner(chain: Chain, origin: TransactionOrigin): NovaSigner { + val metaAccount = accountRepository.requireMetaAccountFor(origin, chain.id) return signerProvider.feeSigner(metaAccount, chain) } + + private data class SubmissionRaw(val extrinsicRaw: String, val submissionOrigin: SubmissionOrigin) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index 30b81723a2..643f1ec8a3 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -116,12 +116,12 @@ class AccountRepositoryImpl( return accountDataSource.selectedMetaAccountFlow() } - override suspend fun findMetaAccount(accountId: ByteArray): MetaAccount? { - return accountDataSource.findMetaAccount(accountId) + override suspend fun findMetaAccount(accountId: ByteArray, chainId: String): MetaAccount? { + return accountDataSource.findMetaAccount(accountId, chainId) } - override suspend fun accountNameFor(accountId: AccountId): String? { - return accountDataSource.accountNameFor(accountId) + override suspend fun accountNameFor(accountId: AccountId, chainId: String): String? { + return accountDataSource.accountNameFor(accountId, chainId) } override suspend fun allMetaAccounts(): List { @@ -251,8 +251,8 @@ class AccountRepositoryImpl( } } - override suspend fun isAccountExists(accountId: AccountId): Boolean { - return accountDataSource.accountExists(accountId) + override suspend fun isAccountExists(accountId: AccountId, chainId: String): Boolean { + return accountDataSource.accountExists(accountId, chainId) } override fun nodesFlow(): Flow> { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 269cc26a92..2074126099 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.scale.EncodableStruct import kotlinx.coroutines.flow.Flow @@ -39,9 +40,10 @@ interface AccountDataSource : SecretStoreV1 { suspend fun getSelectedMetaAccount(): MetaAccount fun selectedMetaAccountFlow(): Flow - suspend fun findMetaAccount(accountId: ByteArray): MetaAccount? - suspend fun accountNameFor(accountId: AccountId): String? + suspend fun findMetaAccount(accountId: ByteArray, chainId: ChainId): MetaAccount? + + suspend fun accountNameFor(accountId: AccountId, chainId: ChainId): String? suspend fun allMetaAccounts(): List @@ -61,7 +63,7 @@ interface AccountDataSource : SecretStoreV1 { suspend fun getSelectedLanguage(): Language suspend fun changeSelectedLanguage(language: Language) - suspend fun accountExists(accountId: AccountId): Boolean + suspend fun accountExists(accountId: AccountId, chainId: ChainId): Boolean suspend fun getMetaAccount(metaId: Long): MetaAccount fun metaAccountFlow(metaId: Long): Flow diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 9cce988484..f8798e9c4f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -31,6 +31,7 @@ import io.novafoundation.nova.feature_account_impl.data.repository.datasource.mi import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import jp.co.soramitsu.fearless_utils.extensions.asEthereumPublicKey import jp.co.soramitsu.fearless_utils.extensions.toAccountId import jp.co.soramitsu.fearless_utils.runtime.AccountId @@ -132,13 +133,13 @@ class AccountDataSourceImpl( override fun selectedMetaAccountFlow(): Flow = selectedMetaAccountFlow - override suspend fun findMetaAccount(accountId: ByteArray): MetaAccount? { - return metaAccountDao.getMetaAccountInfo(accountId) + override suspend fun findMetaAccount(accountId: ByteArray, chainId: ChainId): MetaAccount? { + return metaAccountDao.getMetaAccountInfo(accountId, chainId) ?.let(::mapMetaAccountLocalToMetaAccount) } - override suspend fun accountNameFor(accountId: AccountId): String? { - return metaAccountDao.metaAccountNameFor(accountId) + override suspend fun accountNameFor(accountId: AccountId, chainId: ChainId): String? { + return metaAccountDao.metaAccountNameFor(accountId, chainId) } override suspend fun allMetaAccounts(): List { @@ -190,8 +191,8 @@ class AccountDataSourceImpl( preferences.saveCurrentLanguage(language.iso) } - override suspend fun accountExists(accountId: AccountId): Boolean { - return metaAccountDao.isMetaAccountExists(accountId) + override suspend fun accountExists(accountId: AccountId, chainId: ChainId): Boolean { + return metaAccountDao.isMetaAccountExists(accountId, chainId) } override suspend fun getMetaAccount(metaId: Long): MetaAccount { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/identity/LocalIdentityProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/identity/LocalIdentityProvider.kt index 5f7f06b72e..df37cdfd74 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/identity/LocalIdentityProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/identity/LocalIdentityProvider.kt @@ -13,7 +13,7 @@ class LocalIdentityProvider( ) : IdentityProvider { override suspend fun identityFor(accountId: AccountId, chainId: ChainId): Identity? = withContext(Dispatchers.IO) { - val name = accountRepository.accountNameFor(accountId) + val name = accountRepository.accountNameFor(accountId, chainId) name?.let(::Identity) } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/send/SendInteractor.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/send/SendInteractor.kt index 517197a7c0..314d079fc7 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/send/SendInteractor.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/send/SendInteractor.kt @@ -95,8 +95,8 @@ class SendInteractor( crossChainTransactor.performTransfer(config, transfer, crossChainFeePlanks) } else { getAssetTransfers(transfer).performTransfer(transfer) - .onSuccess { hash -> - walletRepository.insertPendingTransfer(hash, transfer, originFee) + .onSuccess { submission -> + walletRepository.insertPendingTransfer(submission.hash, transfer, originFee) } } } diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/CrowdloanContributeInteractor.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/CrowdloanContributeInteractor.kt index 852594e964..3b0d232328 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/CrowdloanContributeInteractor.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/CrowdloanContributeInteractor.kt @@ -2,7 +2,9 @@ package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute import android.os.Parcelable import io.novafoundation.nova.common.data.network.runtime.binding.ParaId +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn @@ -79,14 +81,14 @@ class CrowdloanContributeInteractor( contribution: BigDecimal, bonusPayload: BonusPayload?, customizationPayload: Parcelable?, - ) = formingSubmission( + ): Fee = formingSubmission( crowdloan = crowdloan, contribution = contribution, bonusPayload = bonusPayload, customizationPayload = customizationPayload, toCalculateFee = true ) { submission, chain, _ -> - extrinsicService.estimateFee(chain, submission) + extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet, submission) } suspend fun contribute( @@ -99,19 +101,17 @@ class CrowdloanContributeInteractor( customContributeManager.getFactoryOrNull(it)?.submitter?.submitOffChain(customizationPayload, bonusPayload, contribution) } - val txHash = formingSubmission( + val extrinsicSubmission = formingSubmission( crowdloan = crowdloan, contribution = contribution, bonusPayload = bonusPayload, toCalculateFee = false, customizationPayload = customizationPayload ) { submission, chain, account -> - val accountId = account.accountIdIn(chain)!! - - extrinsicService.submitExtrinsicWithAnySuitableWallet(chain, accountId) { submission() } + extrinsicService.submitExtrinsic(chain, TransactionOrigin.Wallet(account)) { submission() } }.getOrThrow() - txHash + extrinsicSubmission.hash } private suspend fun formingSubmission( diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/moonbeam/MoonbeamCrowdloanInteractor.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/moonbeam/MoonbeamCrowdloanInteractor.kt index 30f1236fcd..79adf7306c 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/moonbeam/MoonbeamCrowdloanInteractor.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/moonbeam/MoonbeamCrowdloanInteractor.kt @@ -6,7 +6,9 @@ import io.novafoundation.nova.common.utils.LOG_TAG import io.novafoundation.nova.common.utils.asHexString import io.novafoundation.nova.common.utils.sha256 import io.novafoundation.nova.core.model.CryptoType +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn @@ -38,7 +40,6 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import retrofit2.HttpException -import java.math.BigInteger class VerificationError : Exception() @@ -105,10 +106,10 @@ class MoonbeamCrowdloanInteractor( } } - suspend fun calculateTermsFee(): BigInteger = withContext(Dispatchers.Default) { + suspend fun calculateTermsFee(): Fee = withContext(Dispatchers.Default) { val chain = selectedChainAssetState.chain() - extrinsicService.estimateFee(chain) { + extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { systemRemark(fakeRemark()) } } @@ -132,9 +133,10 @@ class MoonbeamCrowdloanInteractor( val agreeRemarkRequest = AgreeRemarkRequest(currentAddress, signedHash) val remark = httpExceptionHandler.wrap { moonbeamApi.agreeRemark(parachainMetadata, agreeRemarkRequest) }.remark - val finalizedStatus = extrinsicService.submitAndWatchExtrinsicAnySuitableWallet(chain, metaAccount.accountIdIn(chain)!!) { + val finalizedStatus = extrinsicService.submitAndWatchExtrinsic(chain, TransactionOrigin.SelectedWallet) { systemRemark(remark.encodeToByteArray()) } + .getOrThrow() .filterIsInstance() .first() diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/custom/moonbeam/terms/MoonbeamCrowdloanTermsViewModel.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/custom/moonbeam/terms/MoonbeamCrowdloanTermsViewModel.kt index 50387757f4..244012114d 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/custom/moonbeam/terms/MoonbeamCrowdloanTermsViewModel.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/custom/moonbeam/terms/MoonbeamCrowdloanTermsViewModel.kt @@ -128,9 +128,7 @@ class MoonbeamCrowdloanTermsViewModel( private fun loadFee() = launch { feeLoaderMixin.loadFee( coroutineScope = this, - feeConstructor = { - interactor.calculateTermsFee() - }, + feeConstructor = { interactor.calculateTermsFee() }, onRetryCancelled = ::backClicked ) } diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/select/CrowdloanContributeViewModel.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/select/CrowdloanContributeViewModel.kt index 406d69cf28..4dc1819c6c 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/select/CrowdloanContributeViewModel.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/select/CrowdloanContributeViewModel.kt @@ -16,7 +16,6 @@ import io.novafoundation.nova.common.validation.CompositeValidation import io.novafoundation.nova.common.validation.ValidationExecutor import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.common.validation.progressConsumer -import io.novafoundation.nova.feature_account_api.data.model.InlineFee import io.novafoundation.nova.feature_crowdloan_impl.R import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.CustomContributeManager import io.novafoundation.nova.feature_crowdloan_impl.di.customCrowdloan.hasExtraBonusFlow @@ -276,7 +275,7 @@ class CrowdloanContributeViewModel( customizationPayload, ) - SimpleFee(InlineFee(fee)) + SimpleFee(fee) }, onRetryCancelled = ::backClicked ) diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/presentation/signExtrinsic/ExternaSignViewModel.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/presentation/signExtrinsic/ExternaSignViewModel.kt index ab4c35b2ef..237f2f741b 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/presentation/signExtrinsic/ExternaSignViewModel.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/presentation/signExtrinsic/ExternaSignViewModel.kt @@ -127,7 +127,7 @@ class ExternaSignViewModel( } private fun maybeLoadFee() { - originFeeMixin?.loadFeeV2( + originFeeMixin?.loadFee( coroutineScope = this, feeConstructor = { interactor.calculateFee() }, onRetryCancelled = {} diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/delegation/delegation/removeVotes/RemoveTrackVotesInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/delegation/delegation/removeVotes/RemoveTrackVotesInteractor.kt index fdc9dec661..40c32e01a0 100644 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/delegation/delegation/removeVotes/RemoveTrackVotesInteractor.kt +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/delegation/delegation/removeVotes/RemoveTrackVotesInteractor.kt @@ -1,12 +1,12 @@ package io.novafoundation.nova.feature_governance_api.domain.delegation.delegation.removeVotes +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus interface RemoveTrackVotesInteractor { - suspend fun calculateFee(trackIds: Collection): Balance + suspend fun calculateFee(trackIds: Collection): Fee suspend fun removeTrackVotes(trackIds: Collection): Result } diff --git a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/vote/VoteReferendumInteractor.kt b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/vote/VoteReferendumInteractor.kt index 47cbc3b2ff..1012f78863 100644 --- a/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/vote/VoteReferendumInteractor.kt +++ b/feature-governance-api/src/main/java/io/novafoundation/nova/feature_governance_api/domain/referendum/vote/VoteReferendumInteractor.kt @@ -1,5 +1,7 @@ package io.novafoundation.nova.feature_governance_api.domain.referendum.vote +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance @@ -14,10 +16,10 @@ interface VoteReferendumInteractor { scope: CoroutineScope ): Flow - suspend fun estimateFee(amount: Balance, conviction: Conviction, referendumId: ReferendumId): Balance + suspend fun estimateFee(amount: Balance, conviction: Conviction, referendumId: ReferendumId): Fee suspend fun vote( vote: AccountVote.Standard, referendumId: ReferendumId, - ): Result + ): Result } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealNewDelegationChooseAmountInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealNewDelegationChooseAmountInteractor.kt index eae1164b8e..c123ba100f 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealNewDelegationChooseAmountInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/RealNewDelegationChooseAmountInteractor.kt @@ -60,7 +60,7 @@ class RealNewDelegationChooseAmountInteractor( val (chain, governanceSource) = useSelectedGovernance() val origin = accountRepository.requireIdOfSelectedMetaAccountIn(chain) - return extrinsicService.estimateMultiFee(chain) { + return extrinsicService.estimateMultiFee(chain, TransactionOrigin.SelectedWallet) { delegate(governanceSource, amount, conviction, delegate, origin, chain, tracks, shouldRemoveOtherTracks) } } @@ -75,7 +75,16 @@ class RealNewDelegationChooseAmountInteractor( val (chain, governanceSource) = useSelectedGovernance() return extrinsicService.submitMultiExtrinsicAwaitingInclusion(chain, TransactionOrigin.SelectedWallet) { origin -> - delegate(governanceSource, amount, conviction, delegate, origin, chain, tracks, shouldRemoveOtherTracks) + delegate( + governanceSource = governanceSource, + amount = amount, + conviction = conviction, + delegate = delegate, + user = origin.requestedOrigin, + chain = chain, + tracks = tracks, + shouldRemoveOtherTracks = shouldRemoveOtherTracks + ) } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/RealRemoveTrackVotesInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/RealRemoveTrackVotesInteractor.kt index d6411b2b63..e91be3669d 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/RealRemoveTrackVotesInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/RealRemoveTrackVotesInteractor.kt @@ -1,7 +1,9 @@ package io.novafoundation.nova.feature_governance_impl.domain.delegation.delegation.removeVotes +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.extrinsic.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion +import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.requireIdOfSelectedMetaAccountIn import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId @@ -10,7 +12,6 @@ import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourc import io.novafoundation.nova.feature_governance_api.data.source.GovernanceSourceRegistry import io.novafoundation.nova.feature_governance_api.domain.delegation.delegation.removeVotes.RemoveTrackVotesInteractor import io.novafoundation.nova.feature_governance_impl.data.GovernanceSharedState -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId @@ -27,11 +28,11 @@ class RealRemoveTrackVotesInteractor( private val accountRepository: AccountRepository, ) : RemoveTrackVotesInteractor { - override suspend fun calculateFee(trackIds: Collection): Balance = withContext(Dispatchers.IO) { + override suspend fun calculateFee(trackIds: Collection): Fee = withContext(Dispatchers.IO) { val (chain, governance) = useSelectedGovernance() val accountId = accountRepository.requireIdOfSelectedMetaAccountIn(chain) - extrinsicService.estimateFee(chain) { + extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { governance.removeVotes(trackIds, extrinsicBuilder = this, chain.id, accountId) } } @@ -39,18 +40,18 @@ class RealRemoveTrackVotesInteractor( override suspend fun removeTrackVotes(trackIds: Collection): Result = withContext(Dispatchers.IO) { val (chain, governance) = useSelectedGovernance() - extrinsicService.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion(chain) { origin -> - governance.removeVotes(trackIds, extrinsicBuilder = this, chain.id, origin) - } + extrinsicService.submitAndWatchExtrinsic(chain, TransactionOrigin.SelectedWallet) { origin -> + governance.removeVotes(trackIds, extrinsicBuilder = this, chain.id, accountIdToRemoveVotes = origin.requestedOrigin) + }.awaitInBlock() } private suspend fun GovernanceSource.removeVotes( trackIds: Collection, extrinsicBuilder: ExtrinsicBuilder, chainId: ChainId, - accountId: AccountId + accountIdToRemoveVotes: AccountId ) { - val votings = convictionVoting.votingFor(accountId, chainId, trackIds) + val votings = convictionVoting.votingFor(accountIdToRemoveVotes, chainId, trackIds) votings.entries.onEach { (trackId, voting) -> voting.votedReferenda().onEach { referendumId -> diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/RevokeDelegationsInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/RevokeDelegationsInteractor.kt index 7c40b48081..ffb0b49812 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/RevokeDelegationsInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/RevokeDelegationsInteractor.kt @@ -47,7 +47,7 @@ class RealRevokeDelegationsInteractor( override suspend fun calculateFee(trackIds: Collection): Fee { val (chain, source) = useSelectedGovernance() - return extrinsicService.estimateMultiFee(chain) { + return extrinsicService.estimateMultiFee(chain, TransactionOrigin.SelectedWallet) { revokeDelegations(source, trackIds) } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt index dca9f662de..174cb5636e 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt @@ -3,8 +3,11 @@ package io.novafoundation.nova.feature_governance_impl.domain.referendum.unlock import io.novafoundation.nova.common.data.memory.ComputationalCache import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber import io.novafoundation.nova.common.utils.flowOfAll +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.extrinsic.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion +import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.data.model.zero import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.OnChainReferendum @@ -49,7 +52,7 @@ import kotlinx.coroutines.withContext interface GovernanceUnlockInteractor { - suspend fun calculateFee(claimable: UnlockChunk.Claimable?): Balance + suspend fun calculateFee(claimable: UnlockChunk.Claimable?): Fee suspend fun unlock(claimable: UnlockChunk.Claimable?): Result @@ -77,8 +80,8 @@ class RealGovernanceUnlockInteractor( private val extrinsicService: ExtrinsicService, ) : GovernanceUnlockInteractor { - override suspend fun calculateFee(claimable: UnlockChunk.Claimable?): Balance { - if (claimable == null) return Balance.ZERO + override suspend fun calculateFee(claimable: UnlockChunk.Claimable?): Fee { + if (claimable == null) return Fee.zero() val governanceSelectedOption = selectedAssetState.selectedOption() val chain = governanceSelectedOption.assetWithChain.chain @@ -86,7 +89,7 @@ class RealGovernanceUnlockInteractor( val metaAccount = accountRepository.getSelectedMetaAccount() val origin = metaAccount.accountIdIn(chain)!! - return extrinsicService.estimateFee(chain) { + return extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { executeUnlock(origin, governanceSelectedOption, claimable) } } @@ -95,22 +98,22 @@ class RealGovernanceUnlockInteractor( val governanceSelectedOption = selectedAssetState.selectedOption() val chain = governanceSelectedOption.assetWithChain.chain - extrinsicService.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion(chain) { origin -> + extrinsicService.submitAndWatchExtrinsic(chain, TransactionOrigin.SelectedWallet) { origin -> if (claimable == null) error("Nothing to claim") - executeUnlock(origin, governanceSelectedOption, claimable) - } + executeUnlock(accountIdToUnlock = origin.requestedOrigin, governanceSelectedOption, claimable) + }.awaitInBlock() } private suspend fun ExtrinsicBuilder.executeUnlock( - origin: AccountId, + accountIdToUnlock: AccountId, selectedGovernanceOption: SupportedGovernanceOption, claimable: UnlockChunk.Claimable ) { val governanceSource = governanceSourceRegistry.sourceFor(selectedGovernanceOption) with(governanceSource.convictionVoting) { - unlock(origin, claimable) + unlock(accountIdToUnlock, claimable) } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/RealVoteReferendumInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/RealVoteReferendumInteractor.kt index 30ce631b43..3a503bb9ed 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/RealVoteReferendumInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/RealVoteReferendumInteractor.kt @@ -3,7 +3,10 @@ package io.novafoundation.nova.feature_governance_impl.domain.referendum.vote import io.novafoundation.nova.common.data.memory.ComputationalCache import io.novafoundation.nova.common.data.network.runtime.binding.BlockNumber import io.novafoundation.nova.common.utils.orZero +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote @@ -68,14 +71,14 @@ class RealVoteReferendumInteractor( } } - override suspend fun estimateFee(amount: Balance, conviction: Conviction, referendumId: ReferendumId): Balance { + override suspend fun estimateFee(amount: Balance, conviction: Conviction, referendumId: ReferendumId): Fee { val governanceOption = selectedChainState.selectedOption() val chain = governanceOption.assetWithChain.chain val vote = AyeVote(amount, conviction) // vote direction does not influence fee estimation val governanceSource = governanceSourceRegistry.sourceFor(governanceOption) - return extrinsicService.estimateFee(chain) { + return extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { with(governanceSource.convictionVoting) { vote(referendumId, vote) } @@ -85,11 +88,11 @@ class RealVoteReferendumInteractor( override suspend fun vote( vote: AccountVote.Standard, referendumId: ReferendumId, - ): Result { + ): Result { val governanceSelectedOption = selectedChainState.selectedOption() val governanceSource = governanceSourceRegistry.sourceFor(governanceSelectedOption) - return extrinsicService.submitExtrinsicWithSelectedWallet(governanceSelectedOption.assetWithChain.chain) { + return extrinsicService.submitExtrinsic(governanceSelectedOption.assetWithChain.chain, TransactionOrigin.SelectedWallet) { with(governanceSource.convictionVoting) { vote(referendumId, vote) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt index 9b696c38e7..e3d9073bc5 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt @@ -32,7 +32,7 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChoose import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWithV2 +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -127,7 +127,7 @@ class NewDelegationChooseAmountViewModel( } init { - originFeeMixin.connectWithV2( + originFeeMixin.connectWith( inputSource1 = amountChooserMixin.backPressuredAmount, inputSource2 = selectedConvictionFlow, scope = this, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt index 65d6904e0b..b0ec2677ac 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt @@ -166,7 +166,7 @@ class RevokeDelegationConfirmViewModel( } private fun loadFee() = launch { - originFeeMixin.loadFeeV2( + originFeeMixin.loadFee( coroutineScope = coroutineScope, feeConstructor = { interactor.calculateFee(payload.trackIds) }, onRetryCancelled = {} diff --git a/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/relaychain/StakingState.kt b/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/relaychain/StakingState.kt index 5131513717..b65e4f059e 100644 --- a/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/relaychain/StakingState.kt +++ b/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/relaychain/StakingState.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_staking_api.domain.model.relaychain +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_staking_api.domain.model.Nominations import io.novafoundation.nova.feature_staking_api.domain.model.ValidatorPrefs import io.novafoundation.nova.runtime.ext.addressOf @@ -53,3 +54,8 @@ sealed class StakingState( ) : Stash(chain, chainAsset, accountId, controllerId, stashId) } } + + +fun StakingState.Stash.stashTransactionOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(stashId) + +fun StakingState.Stash.controllerTransactionOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(controllerId) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/RebagInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/RebagInteractor.kt index a7757846cf..75d081d349 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/RebagInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/RebagInteractor.kt @@ -3,12 +3,14 @@ package io.novafoundation.nova.feature_staking_impl.domain.bagList.rebag import io.novafoundation.nova.common.utils.flowOfAll import io.novafoundation.nova.common.utils.map import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.stashTransactionOrigin import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.rebag import io.novafoundation.nova.feature_staking_impl.data.repository.BagListRepository import io.novafoundation.nova.feature_staking_impl.data.repository.bagListLocatorOrThrow import io.novafoundation.nova.feature_staking_impl.domain.bagList.BagListScoreConverter -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository import kotlinx.coroutines.flow.Flow @@ -17,9 +19,9 @@ import kotlinx.coroutines.flow.filterNotNull interface RebagInteractor { - suspend fun calculateFee(stakingState: StakingState.Stash): Balance + suspend fun calculateFee(stakingState: StakingState.Stash): Fee - suspend fun rebag(stakingState: StakingState.Stash): Result + suspend fun rebag(stakingState: StakingState.Stash): Result fun rebagMovementFlow(stakingState: StakingState.Stash): Flow } @@ -31,14 +33,14 @@ class RealRebagInteractor( private val extrinsicService: ExtrinsicService, ) : RebagInteractor { - override suspend fun calculateFee(stakingState: StakingState.Stash): Balance { - return extrinsicService.estimateFee(stakingState.chain) { + override suspend fun calculateFee(stakingState: StakingState.Stash): Fee { + return extrinsicService.estimateFee(stakingState.chain, stakingState.stashTransactionOrigin()) { rebag(stakingState.stashId) } } - override suspend fun rebag(stakingState: StakingState.Stash): Result { - return extrinsicService.submitExtrinsicWithAnySuitableWallet(stakingState.chain, stakingState.stashId) { + override suspend fun rebag(stakingState: StakingState.Stash): Result { + return extrinsicService.submitExtrinsic(stakingState.chain, stakingState.stashTransactionOrigin()) { rebag(stakingState.stashId) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/bondMore/NominationPoolsBondMoreInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/bondMore/NominationPoolsBondMoreInteractor.kt index f40d4a20b5..8a0f4b66f5 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/bondMore/NominationPoolsBondMoreInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/bondMore/NominationPoolsBondMoreInteractor.kt @@ -1,6 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.bondMore +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.calls.bondExtra import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.calls.nominationPools @@ -11,9 +14,9 @@ import kotlinx.coroutines.withContext interface NominationPoolsBondMoreInteractor { - suspend fun estimateFee(bondMoreAmount: Balance): Balance + suspend fun estimateFee(bondMoreAmount: Balance): Fee - suspend fun bondMore(bondMoreAmount: Balance): Result + suspend fun bondMore(bondMoreAmount: Balance): Result } class RealNominationPoolsBondMoreInteractor( @@ -21,17 +24,17 @@ class RealNominationPoolsBondMoreInteractor( private val stakingSharedState: StakingSharedState, ) : NominationPoolsBondMoreInteractor { - override suspend fun estimateFee(bondMoreAmount: Balance): Balance { + override suspend fun estimateFee(bondMoreAmount: Balance): Fee { return withContext(Dispatchers.IO) { - extrinsicService.estimateFee(stakingSharedState.chain()) { + extrinsicService.estimateFee(stakingSharedState.chain(), TransactionOrigin.SelectedWallet) { nominationPools.bondExtra(bondMoreAmount) } } } - override suspend fun bondMore(bondMoreAmount: Balance): Result { + override suspend fun bondMore(bondMoreAmount: Balance): Result { return withContext(Dispatchers.IO) { - extrinsicService.submitExtrinsicWithSelectedWallet(stakingSharedState.chain()) { + extrinsicService.submitExtrinsic(stakingSharedState.chain(), TransactionOrigin.SelectedWallet) { nominationPools.bondExtra(bondMoreAmount) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/NominationPoolsClaimRewardsInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/NominationPoolsClaimRewardsInteractor.kt index 900213fdf6..9215becac1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/NominationPoolsClaimRewardsInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/NominationPoolsClaimRewardsInteractor.kt @@ -1,6 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.claimRewards +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.calls.NominationPoolBondExtraSource import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.calls.bondExtra @@ -22,9 +25,9 @@ interface NominationPoolsClaimRewardsInteractor { fun pendingRewardsFlow(): Flow - suspend fun estimateFee(shouldRestake: Boolean): Balance + suspend fun estimateFee(shouldRestake: Boolean): Fee - suspend fun claimRewards(shouldRestake: Boolean): Result + suspend fun claimRewards(shouldRestake: Boolean): Result } class RealNominationPoolsClaimRewardsInteractor( @@ -43,17 +46,17 @@ class RealNominationPoolsClaimRewardsInteractor( } } - override suspend fun estimateFee(shouldRestake: Boolean): Balance { + override suspend fun estimateFee(shouldRestake: Boolean): Fee { return withContext(Dispatchers.IO) { - extrinsicService.estimateFee(stakingSharedState.chain()) { + extrinsicService.estimateFee(stakingSharedState.chain(), TransactionOrigin.SelectedWallet) { claimRewards(shouldRestake) } } } - override suspend fun claimRewards(shouldRestake: Boolean): Result { + override suspend fun claimRewards(shouldRestake: Boolean): Result { return withContext(Dispatchers.IO) { - extrinsicService.submitExtrinsicWithSelectedWallet(stakingSharedState.chain()) { + extrinsicService.submitExtrinsic(stakingSharedState.chain(), TransactionOrigin.SelectedWallet) { claimRewards(shouldRestake) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/NominationPoolsRedeemInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/NominationPoolsRedeemInteractor.kt index aa94870dee..7e7d5ecce8 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/NominationPoolsRedeemInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/NominationPoolsRedeemInteractor.kt @@ -2,7 +2,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.redee import io.novafoundation.nova.common.utils.flowOfAll import io.novafoundation.nova.common.utils.isZero +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.bondedAccountOf import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository @@ -25,13 +27,12 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.withContext -import java.math.BigInteger interface NominationPoolsRedeemInteractor { fun redeemAmountFlow(poolMember: PoolMember, computationScope: CoroutineScope): Flow - suspend fun estimateFee(poolMember: PoolMember): BigInteger + suspend fun estimateFee(poolMember: PoolMember): Fee suspend fun redeem(poolMember: PoolMember): Result } @@ -58,9 +59,9 @@ class RealNominationPoolsRedeemInteractor( } } - override suspend fun estimateFee(poolMember: PoolMember): BigInteger { + override suspend fun estimateFee(poolMember: PoolMember): Fee { return withContext(Dispatchers.IO) { - extrinsicService.estimateFee(stakingSharedState.chain()) { + extrinsicService.estimateFee(stakingSharedState.chain(), TransactionOrigin.SelectedWallet) { redeem(poolMember) } } @@ -71,7 +72,7 @@ class RealNominationPoolsRedeemInteractor( val chain = stakingSharedState.chain() val activeEra = stakingRepository.getActiveEraIndex(chain.id) - extrinsicService.submitExtrinsicWithSelectedWalletV2(chain) { + extrinsicService.submitExtrinsic(chain, TransactionOrigin.SelectedWallet) { redeem(poolMember) }.map { val totalAfterRedeem = poolMember.totalPointsAfterRedeemAt(activeEra) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/NominationPoolsUnbondInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/NominationPoolsUnbondInteractor.kt index 85be0054c3..909e2e2a79 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/NominationPoolsUnbondInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/NominationPoolsUnbondInteractor.kt @@ -1,6 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.unbond +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.bondedAccountOf import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState @@ -28,9 +31,9 @@ interface NominationPoolsUnbondInteractor { fun poolMemberStateFlow(computationScope: CoroutineScope): Flow - suspend fun estimateFee(poolMember: PoolMember, amount: Balance): Balance + suspend fun estimateFee(poolMember: PoolMember, amount: Balance): Fee - suspend fun unbond(poolMember: PoolMember, amount: Balance): Result + suspend fun unbond(poolMember: PoolMember, amount: Balance): Result } class RealNominationPoolsUnbondInteractor( @@ -55,21 +58,21 @@ class RealNominationPoolsUnbondInteractor( } } - override suspend fun estimateFee(poolMember: PoolMember, amount: Balance): Balance { + override suspend fun estimateFee(poolMember: PoolMember, amount: Balance): Fee { return withContext(Dispatchers.IO) { val chain = stakingSharedState.chain() - extrinsicService.estimateFee(chain) { + extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { nominationPools.unbond(poolMember, amount, chain.id) } } } - override suspend fun unbond(poolMember: PoolMember, amount: Balance): Result { + override suspend fun unbond(poolMember: PoolMember, amount: Balance): Result { return withContext(Dispatchers.IO) { val chain = stakingSharedState.chain() - extrinsicService.submitExtrinsicWithSelectedWallet(stakingSharedState.chain()) { + extrinsicService.submitExtrinsic(stakingSharedState.chain(), TransactionOrigin.SelectedWallet) { nominationPools.unbond(poolMember, amount, chain.id) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/ParachainStakingRebondInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/ParachainStakingRebondInteractor.kt index c91556cd7e..5d034426a5 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/ParachainStakingRebondInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/ParachainStakingRebondInteractor.kt @@ -1,8 +1,10 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.rebond import io.novafoundation.nova.common.utils.orZero +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.extrinsic.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion +import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.domain.model.parachain.DelegatorState import io.novafoundation.nova.feature_staking_impl.data.parachainStaking.network.calls.cancelDelegationRequest import io.novafoundation.nova.feature_staking_impl.data.parachainStaking.repository.DelegatorStateRepository @@ -15,7 +17,7 @@ import java.math.BigInteger interface ParachainStakingRebondInteractor { - suspend fun estimateFee(collatorId: AccountId): BigInteger + suspend fun estimateFee(collatorId: AccountId): Fee suspend fun rebondAmount( delegatorState: DelegatorState, @@ -31,8 +33,8 @@ class RealParachainStakingRebondInteractor( private val selectedAssetState: AnySelectedAssetOptionSharedState, ) : ParachainStakingRebondInteractor { - override suspend fun estimateFee(collatorId: AccountId): BigInteger = withContext(Dispatchers.IO) { - extrinsicService.estimateFee(selectedAssetState.chain()) { + override suspend fun estimateFee(collatorId: AccountId): Fee = withContext(Dispatchers.IO) { + extrinsicService.estimateFee(selectedAssetState.chain(), TransactionOrigin.SelectedWallet) { cancelDelegationRequest(collatorId) } } @@ -49,8 +51,8 @@ class RealParachainStakingRebondInteractor( } override suspend fun rebond(collatorId: AccountId): Result<*> = withContext(Dispatchers.IO) { - extrinsicService.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion(selectedAssetState.chain()) { + extrinsicService.submitAndWatchExtrinsic(selectedAssetState.chain(), TransactionOrigin.SelectedWallet) { cancelDelegationRequest(collatorId) - } + }.awaitInBlock() } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/ParachainStakingRedeemInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/ParachainStakingRedeemInteractor.kt index 4fda73d76b..49b528db39 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/ParachainStakingRedeemInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/ParachainStakingRedeemInteractor.kt @@ -2,9 +2,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.rede import io.novafoundation.nova.common.utils.isZero import io.novafoundation.nova.common.utils.sumByBigInteger +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.extrinsic.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion +import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.domain.model.parachain.DelegatorState import io.novafoundation.nova.feature_staking_api.domain.model.parachain.ScheduledDelegationRequest import io.novafoundation.nova.feature_staking_api.domain.model.parachain.activeBonded @@ -21,7 +23,7 @@ import java.math.BigInteger interface ParachainStakingRedeemInteractor { - suspend fun estimateFee(delegatorState: DelegatorState): BigInteger + suspend fun estimateFee(delegatorState: DelegatorState): Fee suspend fun redeemableAmount(delegatorState: DelegatorState): BigInteger @@ -34,8 +36,8 @@ class RealParachainStakingRedeemInteractor( private val delegatorStateRepository: DelegatorStateRepository, ) : ParachainStakingRedeemInteractor { - override suspend fun estimateFee(delegatorState: DelegatorState): BigInteger = withContext(Dispatchers.Default) { - extrinsicService.estimateFee(delegatorState.chain) { + override suspend fun estimateFee(delegatorState: DelegatorState): Fee = withContext(Dispatchers.Default) { + extrinsicService.estimateFee(delegatorState.chain, TransactionOrigin.SelectedWallet) { redeem(delegatorState) } } @@ -47,11 +49,13 @@ class RealParachainStakingRedeemInteractor( } override suspend fun redeem(delegatorState: DelegatorState): Result = withContext(Dispatchers.Default) { - extrinsicService.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion(delegatorState.chain) { + extrinsicService.submitAndWatchExtrinsic(delegatorState.chain, TransactionOrigin.SelectedWallet) { redeem(delegatorState) - }.map { - RedeemConsequences(willKillStash = delegatorState.activeBonded.isZero) } + .awaitInBlock() + .map { + RedeemConsequences(willKillStash = delegatorState.activeBonded.isZero) + } } private suspend fun ExtrinsicBuilder.redeem(delegatorState: DelegatorState) { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/StartParachainStakingInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/StartParachainStakingInteractor.kt index b42dd703ed..c5e39c4578 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/StartParachainStakingInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/StartParachainStakingInteractor.kt @@ -1,9 +1,13 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.start import io.novafoundation.nova.common.utils.parachainStaking +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_staking_api.domain.model.parachain.DelegatorState import io.novafoundation.nova.feature_staking_api.domain.model.parachain.delegationsCount import io.novafoundation.nova.feature_staking_api.domain.model.parachain.hasDelegation @@ -23,16 +27,14 @@ import jp.co.soramitsu.fearless_utils.runtime.definitions.types.primitives.Fixed import jp.co.soramitsu.fearless_utils.runtime.definitions.types.skipAliases import jp.co.soramitsu.fearless_utils.runtime.metadata.call import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import java.math.BigInteger interface StartParachainStakingInteractor { - suspend fun estimateFee(amount: BigInteger, collatorId: AccountId?): BigInteger + suspend fun estimateFee(amount: BigInteger, collatorId: AccountId?): Fee - suspend fun delegate(amount: BigInteger, collator: AccountId): Result<*> + suspend fun delegate(amount: BigInteger, collator: AccountId): Result suspend fun checkDelegationsLimit(delegatorState: DelegatorState): DelegationsLimit } @@ -47,14 +49,14 @@ class RealStartParachainStakingInteractor( private val candidatesRepository: CandidatesRepository, ) : StartParachainStakingInteractor { - override suspend fun estimateFee(amount: BigInteger, collatorId: AccountId?): BigInteger { + override suspend fun estimateFee(amount: BigInteger, collatorId: AccountId?): Fee { val (chain, chainAsset) = singleAssetSharedState.chainAndAsset() val metaAccount = accountRepository.getSelectedMetaAccount() val accountId = metaAccount.accountIdIn(chain)!! val currentDelegationState = delegatorStateRepository.getDelegationState(chain, chainAsset, accountId) - return extrinsicService.estimateFee(chain) { + return extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { if (collatorId != null && currentDelegationState.hasDelegation(collatorId)) { delegatorBondMore( candidate = collatorId, @@ -71,15 +73,15 @@ class RealStartParachainStakingInteractor( } } - override suspend fun delegate(amount: BigInteger, collator: AccountId) = withContext(Dispatchers.Default) { + override suspend fun delegate(amount: BigInteger, collator: AccountId): Result = withContext(Dispatchers.Default) { runCatching { val (chain, chainAsset) = singleAssetSharedState.chainAndAsset() val metaAccount = accountRepository.getSelectedMetaAccount() - val accountId = metaAccount.accountIdIn(chain)!! + val accountId = metaAccount.requireAccountIdIn(chain) val currentDelegationState = delegatorStateRepository.getDelegationState(chain, chainAsset, accountId) - extrinsicService.submitAndWatchExtrinsicAnySuitableWallet(chain, accountId) { + extrinsicService.submitAndWatchExtrinsic(chain, TransactionOrigin.SelectedWallet) { if (currentDelegationState.hasDelegation(collator)) { delegatorBondMore( candidate = collator, @@ -95,9 +97,7 @@ class RealStartParachainStakingInteractor( delegationCount = currentDelegationState.delegationsCount.toBigInteger() ) } - } - .filterIsInstance() - .first() + }.awaitInBlock().getOrThrow() } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/ParachainStakingUnbondInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/ParachainStakingUnbondInteractor.kt index 9032fbefaa..248a208f23 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/ParachainStakingUnbondInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/ParachainStakingUnbondInteractor.kt @@ -1,8 +1,10 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.unbond import io.novafoundation.nova.common.utils.orZero +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.extrinsic.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion +import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.domain.model.parachain.DelegatorState import io.novafoundation.nova.feature_staking_api.domain.model.parachain.delegationAmountTo import io.novafoundation.nova.feature_staking_impl.data.parachainStaking.network.calls.scheduleBondLess @@ -20,7 +22,7 @@ import java.math.BigInteger interface ParachainStakingUnbondInteractor { - suspend fun estimateFee(amount: BigInteger, collatorId: AccountId): BigInteger + suspend fun estimateFee(amount: BigInteger, collatorId: AccountId): Fee suspend fun unbond(amount: BigInteger, collator: AccountId): Result<*> @@ -37,10 +39,10 @@ class RealParachainStakingUnbondInteractor( private val collatorsUseCase: CollatorsUseCase, ) : ParachainStakingUnbondInteractor { - override suspend fun estimateFee(amount: BigInteger, collatorId: AccountId): BigInteger { + override suspend fun estimateFee(amount: BigInteger, collatorId: AccountId): Fee { val chain = selectedAssetSharedState.chain() - return extrinsicService.estimateFee(chain) { + return extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { unbond(amount, collatorId) } } @@ -48,9 +50,9 @@ class RealParachainStakingUnbondInteractor( override suspend fun unbond(amount: BigInteger, collator: AccountId): Result<*> = withContext(Dispatchers.IO) { val chain = selectedAssetSharedState.chain() - extrinsicService.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion(chain) { + extrinsicService.submitAndWatchExtrinsic(chain, TransactionOrigin.SelectedWallet) { unbond(amount, collator) - } + }.awaitInBlock() } override suspend fun canUnbond(fromCollator: AccountId, delegatorState: DelegatorState): Boolean = withContext(Dispatchers.IO) { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/YieldBoostInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/YieldBoostInteractor.kt index bd464bfee4..427fcab723 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/YieldBoostInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/YieldBoostInteractor.kt @@ -2,8 +2,10 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.yiel import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.common.utils.orZero +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.extrinsic.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion +import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository.OptimalAutomationRequest import io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository.TuringAutomationTask import io.novafoundation.nova.feature_staking_api.data.parachainStaking.turing.repository.TuringAutomationTasksRepository @@ -23,7 +25,6 @@ import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import java.math.BigInteger import kotlin.math.roundToLong import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.hours @@ -41,7 +42,7 @@ interface YieldBoostInteractor { suspend fun calculateFee( configuration: YieldBoostConfiguration, activeTasks: List, - ): BigInteger + ): Fee suspend fun setYieldBoost( configuration: YieldBoostConfiguration, @@ -63,10 +64,10 @@ class RealYieldBoostInteractor( override suspend fun calculateFee( configuration: YieldBoostConfiguration, activeTasks: List - ): BigInteger { + ): Fee { val chain = singleAssetSharedState.chain() - return extrinsicService.estimateFee(chain) { + return extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { setYieldBoost(chain, activeTasks, configuration) } } @@ -74,9 +75,9 @@ class RealYieldBoostInteractor( override suspend fun setYieldBoost(configuration: YieldBoostConfiguration, activeTasks: List): Result { val chain = singleAssetSharedState.chain() - return extrinsicService.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion(chain) { + return extrinsicService.submitAndWatchExtrinsic(chain, TransactionOrigin.SelectedWallet) { setYieldBoost(chain, activeTasks, configuration) - } + }.awaitInBlock() } override suspend fun optimalYieldBoostParameters(delegatorState: DelegatorState, collatorId: AccountId): YieldBoostParameters { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/payout/PayoutInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/payout/PayoutInteractor.kt index 8b4457cfdf..35dc6c4166 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/payout/PayoutInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/payout/PayoutInteractor.kt @@ -25,7 +25,7 @@ class PayoutInteractor( suspend fun estimatePayoutFee(payouts: List): Fee { return withContext(Dispatchers.IO) { - extrinsicService.estimateMultiFee(stakingSharedState.chain()) { + extrinsicService.estimateMultiFee(stakingSharedState.chain(), TransactionOrigin.SelectedWallet) { payoutMultiple(payouts) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/setup/ChangeValidatorsInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/setup/ChangeValidatorsInteractor.kt index 5050fc664e..2f7fe5b501 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/setup/ChangeValidatorsInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/setup/ChangeValidatorsInteractor.kt @@ -1,9 +1,12 @@ package io.novafoundation.nova.feature_staking_impl.domain.setup import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.controllerTransactionOrigin import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.nominate -import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.ext.multiAddressOf import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.state.chain @@ -11,29 +14,27 @@ import jp.co.soramitsu.fearless_utils.extensions.fromHex import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.math.BigInteger class ChangeValidatorsInteractor( private val extrinsicService: ExtrinsicService, private val stakingSharedState: StakingSharedState, ) { - suspend fun estimateFee(validatorAccountIds: List): BigInteger { + suspend fun estimateFee(validatorAccountIds: List, stakingState: StakingState.Stash): Fee { val chain = stakingSharedState.chain() - return extrinsicService.estimateFee(chain) { + return extrinsicService.estimateFee(chain, stakingState.controllerTransactionOrigin()) { formExtrinsic(chain, validatorAccountIds) } } suspend fun changeValidators( - controllerAddress: String, + stakingState: StakingState.Stash, validatorAccountIds: List - ): Result = withContext(Dispatchers.Default) { + ): Result = withContext(Dispatchers.Default) { val chain = stakingSharedState.chain() - val accountId = chain.accountIdOf(controllerAddress) - extrinsicService.submitExtrinsicWithAnySuitableWallet(chain, accountId) { + extrinsicService.submitExtrinsic(chain, stakingState.controllerTransactionOrigin()) { formExtrinsic(chain, validatorAccountIds) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/bond/BondMoreInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/bond/BondMoreInteractor.kt index 58248e724d..13acfaba3b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/bond/BondMoreInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/bond/BondMoreInteractor.kt @@ -1,6 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.bond +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.intoOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.stashTransactionOrigin import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.bondMore import io.novafoundation.nova.runtime.ext.accountIdOf @@ -14,22 +19,22 @@ class BondMoreInteractor( private val stakingSharedState: StakingSharedState, ) { - suspend fun estimateFee(amount: BigInteger): BigInteger { + suspend fun estimateFee(amount: BigInteger, stakingState: StakingState.Stash): Fee { return withContext(Dispatchers.IO) { val chain = stakingSharedState.chain() - extrinsicService.estimateFee(chain) { + extrinsicService.estimateFee(chain, stakingState.stashTransactionOrigin()) { bondMore(amount) } } } - suspend fun bondMore(accountAddress: String, amount: BigInteger): Result { + suspend fun bondMore(stashAddress: String, amount: BigInteger): Result { return withContext(Dispatchers.IO) { val chain = stakingSharedState.chain() - val accountId = chain.accountIdOf(accountAddress) + val accountId = chain.accountIdOf(stashAddress) - extrinsicService.submitExtrinsicWithAnySuitableWallet(chain, accountId) { + extrinsicService.submitExtrinsic(chain, accountId.intoOrigin()) { bondMore(amount) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/controller/ControllerInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/controller/ControllerInteractor.kt index 969b3f3b81..690a2e0e0c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/controller/ControllerInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/controller/ControllerInteractor.kt @@ -1,6 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.controller +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.intoOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.stashTransactionOrigin import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.setController import io.novafoundation.nova.feature_staking_impl.data.repository.ControllersDeprecationStage @@ -10,7 +15,6 @@ import io.novafoundation.nova.runtime.ext.multiAddressOf import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.math.BigInteger class ControllerInteractor( private val extrinsicService: ExtrinsicService, @@ -26,22 +30,22 @@ class ControllerInteractor( } } - suspend fun estimateFee(controllerAccountAddress: String): BigInteger { + suspend fun estimateFee(controllerAccountAddress: String, stakingState: StakingState.Stash): Fee { return withContext(Dispatchers.IO) { val chain = sharedStakingSate.chain() - extrinsicService.estimateFee(chain) { + extrinsicService.estimateFee(chain, stakingState.stashTransactionOrigin()) { setController(chain.multiAddressOf(controllerAccountAddress)) } } } - suspend fun setController(stashAccountAddress: String, controllerAccountAddress: String): Result { + suspend fun setController(stashAccountAddress: String, controllerAccountAddress: String): Result { return withContext(Dispatchers.IO) { val chain = sharedStakingSate.chain() val accountId = chain.accountIdOf(stashAccountAddress) - extrinsicService.submitExtrinsicWithAnySuitableWallet(chain, accountId) { + extrinsicService.submitExtrinsic(chain, accountId.intoOrigin()) { setController(chain.multiAddressOf(controllerAccountAddress)) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/rebond/RebondInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/rebond/RebondInteractor.kt index 23d947e201..c5859a02ac 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/rebond/RebondInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/rebond/RebondInteractor.kt @@ -1,7 +1,10 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.rebond import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.controllerTransactionOrigin import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.rebond import io.novafoundation.nova.runtime.state.chain @@ -14,19 +17,19 @@ class RebondInteractor( private val sharedStakingSate: StakingSharedState ) { - suspend fun estimateFee(amount: BigInteger): BigInteger { + suspend fun estimateFee(amount: BigInteger, stakingState: StakingState.Stash): Fee { return withContext(Dispatchers.IO) { val chain = sharedStakingSate.chain() - extrinsicService.estimateFee(chain) { + extrinsicService.estimateFee(chain, stakingState.controllerTransactionOrigin()) { rebond(amount) } } } - suspend fun rebond(stashState: StakingState.Stash, amount: BigInteger): Result { + suspend fun rebond(stashState: StakingState.Stash, amount: BigInteger): Result { return withContext(Dispatchers.IO) { - extrinsicService.submitExtrinsicWithAnySuitableWallet(stashState.chain, stashState.controllerId) { + extrinsicService.submitExtrinsic(stashState.chain, stashState.controllerTransactionOrigin()) { rebond(amount) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/redeem/RedeemInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/redeem/RedeemInteractor.kt index bb5e513c05..d2151b8bb2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/redeem/RedeemInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/redeem/RedeemInteractor.kt @@ -2,9 +2,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.redeem import io.novafoundation.nova.common.utils.isZero import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.domain.model.numberOfSlashingSpans import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.controllerTransactionOrigin import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.withdrawUnbonded import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import kotlinx.coroutines.Dispatchers @@ -16,9 +18,9 @@ class RedeemInteractor( private val stakingRepository: StakingRepository, ) { - suspend fun estimateFee(stakingState: StakingState.Stash): BigInteger { + suspend fun estimateFee(stakingState: StakingState.Stash): Fee { return withContext(Dispatchers.IO) { - extrinsicService.estimateFee(stakingState.chain) { + extrinsicService.estimateFee(stakingState.chain, stakingState.controllerTransactionOrigin()) { withdrawUnbonded(getSlashingSpansNumber(stakingState)) } } @@ -26,7 +28,7 @@ class RedeemInteractor( suspend fun redeem(stakingState: StakingState.Stash, asset: Asset): Result { return withContext(Dispatchers.IO) { - extrinsicService.submitExtrinsicWithAnySuitableWallet(stakingState.chain, stakingState.controllerId) { + extrinsicService.submitExtrinsic(stakingState.chain, stakingState.controllerTransactionOrigin()) { withdrawUnbonded(getSlashingSpansNumber(stakingState)) }.map { RedeemConsequences(willKillStash = asset.isRedeemingAll()) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/rewardDestination/ChangeRewardDestinationInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/rewardDestination/ChangeRewardDestinationInteractor.kt index 47cbe159bb..4e7d08eece 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/rewardDestination/ChangeRewardDestinationInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/rewardDestination/ChangeRewardDestinationInteractor.kt @@ -1,12 +1,14 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.rewardDestination import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.domain.model.RewardDestination import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.controllerTransactionOrigin import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.setPayee import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.math.BigInteger class ChangeRewardDestinationInteractor( private val extrinsicService: ExtrinsicService @@ -15,8 +17,8 @@ class ChangeRewardDestinationInteractor( suspend fun estimateFee( stashState: StakingState.Stash, rewardDestination: RewardDestination, - ): BigInteger = withContext(Dispatchers.IO) { - extrinsicService.estimateFee(stashState.chain) { + ): Fee = withContext(Dispatchers.IO) { + extrinsicService.estimateFee(stashState.chain, stashState.controllerTransactionOrigin()) { setPayee(rewardDestination) } } @@ -24,8 +26,8 @@ class ChangeRewardDestinationInteractor( suspend fun changeRewardDestination( stashState: StakingState.Stash, rewardDestination: RewardDestination, - ): Result = withContext(Dispatchers.IO) { - extrinsicService.submitExtrinsicWithAnySuitableWallet(stashState.chain, stashState.controllerId) { + ): Result = withContext(Dispatchers.IO) { + extrinsicService.submitExtrinsic(stashState.chain, stashState.controllerTransactionOrigin()) { setPayee(rewardDestination) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/StartMultiStakingInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/StartMultiStakingInteractor.kt index f12495ba4a..71d4c609ec 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/StartMultiStakingInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/StartMultiStakingInteractor.kt @@ -1,7 +1,8 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.start.common +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.extrinsic.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion +import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_staking_impl.data.chain @@ -25,7 +26,7 @@ class RealStartMultiStakingInteractor( override suspend fun calculateFee(selection: StartMultiStakingSelection): Fee { return withContext(Dispatchers.IO) { - extrinsicService.estimateFeeV2(selection.stakingOption.chain) { + extrinsicService.estimateFee(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) { startStaking(selection) } } @@ -33,9 +34,9 @@ class RealStartMultiStakingInteractor( override suspend fun startStaking(selection: StartMultiStakingSelection): Result { return withContext(Dispatchers.IO) { - extrinsicService.submitExtrinsicWithSelectedWalletAndWaitBlockInclusion(selection.stakingOption.chain) { + extrinsicService.submitAndWatchExtrinsic(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) { startStaking(selection) - } + }.awaitInBlock() } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/unbond/UnbondInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/unbond/UnbondInteractor.kt index 1049b7ff15..e71a0f622d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/unbond/UnbondInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/unbond/UnbondInteractor.kt @@ -3,8 +3,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.unbond import io.novafoundation.nova.common.utils.flowOfAll import io.novafoundation.nova.common.utils.sumByBigInteger import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.controllerTransactionOrigin import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.chill import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.calls.unbond @@ -32,9 +35,9 @@ class UnbondInteractor( stashState: StakingState.Stash, currentBondedBalance: BigInteger, amount: BigInteger - ): BigInteger { + ): Fee { return withContext(Dispatchers.IO) { - extrinsicService.estimateFee(stashState.chain) { + extrinsicService.estimateFee(stashState.chain, stashState.controllerTransactionOrigin()) { constructUnbondExtrinsic(stashState, currentBondedBalance, amount) } } @@ -44,9 +47,9 @@ class UnbondInteractor( stashState: StakingState.Stash, currentBondedBalance: BigInteger, amount: BigInteger - ): Result { + ): Result { return withContext(Dispatchers.IO) { - extrinsicService.submitExtrinsicWithAnySuitableWallet(stashState.chain, stashState.controllerId) { + extrinsicService.submitExtrinsic(stashState.chain, stashState.controllerTransactionOrigin()) { constructUnbondExtrinsic(stashState, currentBondedBalance, amount) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/AccountRequiredValidation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/AccountRequiredValidation.kt index 3327c3be35..29104f1dee 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/AccountRequiredValidation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/AccountRequiredValidation.kt @@ -19,7 +19,7 @@ class AccountRequiredValidation( val accountAddress = accountAddressExtractor(value) val chain = sharedState.chain() - return if (accountRepository.isAccountExists(chain.accountIdOf(accountAddress))) { + return if (accountRepository.isAccountExists(chain.accountIdOf(accountAddress), chain.id)) { ValidationStatus.Valid() } else { ValidationStatus.NotValid(DefaultFailureLevel.ERROR, errorProducer(accountAddress)) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt index 248f50d0a2..fcb9533afe 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt @@ -135,7 +135,7 @@ class ConfirmPayoutViewModel( } private fun loadFee() { - feeLoaderMixin.loadFeeV2( + feeLoaderMixin.loadFee( coroutineScope = viewModelScope, feeConstructor = { payoutInteractor.estimatePayoutFee(payouts) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/select/SelectBondMoreViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/select/SelectBondMoreViewModel.kt index 8b5f340b2d..1dbea6242b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/select/SelectBondMoreViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/select/SelectBondMoreViewModel.kt @@ -96,7 +96,7 @@ class SelectBondMoreViewModel( feeConstructor = { token -> val amountInPlanks = token.planksFromAmount(amount) - bondMoreInteractor.estimateFee(amountInPlanks) + bondMoreInteractor.estimateFee(amountInPlanks, accountStakingFlow.first()) }, onRetryCancelled = ::backClicked ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt index 4e37f11b0b..cbbda995d1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt @@ -177,7 +177,7 @@ class SetControllerViewModel( private fun loadFee() { feeLoaderMixin.loadFee( coroutineScope = viewModelScope, - feeConstructor = { interactor.estimateFee(controllerAddress()) }, + feeConstructor = { interactor.estimateFee(controllerAddress(), accountStakingFlow.first()) }, onRetryCancelled = ::backClicked ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt index 9da994f3ab..b007228e05 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt @@ -110,7 +110,7 @@ class ConfirmRebondViewModel( feeConstructor = { token -> val amountInPlanks = token.planksFromAmount(payload.amount) - rebondInteractor.estimateFee(amountInPlanks) + rebondInteractor.estimateFee(amountInPlanks, accountStakingFlow.first()) }, onRetryCancelled = ::backClicked ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/custom/CustomRebondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/custom/CustomRebondViewModel.kt index d5d78a2467..6d0dce006a 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/custom/CustomRebondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/custom/CustomRebondViewModel.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import java.math.BigDecimal -import kotlin.time.ExperimentalTime class CustomRebondViewModel( private val router: StakingRouter, @@ -87,7 +86,6 @@ class CustomRebondViewModel( router.back() } - @OptIn(ExperimentalTime::class) private fun listenFee() { amountChooserMixin.backPressuredAmount .onEach { loadFee(it) } @@ -100,7 +98,7 @@ class CustomRebondViewModel( feeConstructor = { token -> val amountInPlanks = token.planksFromAmount(amount) - rebondInteractor.estimateFee(amountInPlanks) + rebondInteractor.estimateFee(amountInPlanks, accountStakingFlow.first()) }, onRetryCancelled = ::backClicked ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupAmount/SetupAmountMultiStakingViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupAmount/SetupAmountMultiStakingViewModel.kt index fdaedd4bb7..b9b4c85041 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupAmount/SetupAmountMultiStakingViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupAmount/SetupAmountMultiStakingViewModel.kt @@ -29,7 +29,7 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChoose import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.setAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWithV2 +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel import kotlinx.coroutines.Dispatchers @@ -190,7 +190,7 @@ class SetupAmountMultiStakingViewModel( } private fun runFeeUpdates() { - feeLoaderMixin.connectWithV2( + feeLoaderMixin.connectWith( inputSource = currentSelectionFlow .filterNotNull() .debounce(DEBOUNCE_RATE_MILLIS.milliseconds), diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/select/SelectUnbondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/select/SelectUnbondViewModel.kt index b1bd3d9e33..55fe146265 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/select/SelectUnbondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/select/SelectUnbondViewModel.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import java.math.BigDecimal -import kotlin.time.ExperimentalTime class SelectUnbondViewModel( private val router: StakingRouter, @@ -82,7 +81,6 @@ class SelectUnbondViewModel( router.back() } - @OptIn(ExperimentalTime::class) private fun listenFee() { amountMixin.backPressuredAmount .onEach { loadFee(it) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt index f5bfabcad3..4f66ebedea 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt @@ -128,7 +128,7 @@ class ConfirmChangeValidatorsViewModel( private fun loadFee() { feeLoaderMixin.loadFee( coroutineScope = viewModelScope, - feeConstructor = { changeValidatorsInteractor.estimateFee(prepareNominations()) }, + feeConstructor = { changeValidatorsInteractor.estimateFee(prepareNominations(), stashFlow.first()) }, onRetryCancelled = ::backClicked ) } @@ -155,7 +155,7 @@ class ConfirmChangeValidatorsViewModel( private fun sendTransaction() = launch { val setupResult = changeValidatorsInteractor.changeValidators( - controllerAddress = controllerAddressFlow.first(), + stakingState = stashFlow.first(), validatorAccountIds = prepareNominations(), ) diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt index 25461c42af..e55eae1416 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.common.utils.Percent import io.novafoundation.nova.common.utils.assetConversion import io.novafoundation.nova.common.utils.mutableMultiMapOf import io.novafoundation.nova.common.utils.put +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee @@ -112,16 +113,17 @@ private class AssetConversionExchange( } override suspend fun estimateFee(args: SwapExecuteArgs): AssetExchangeFee { - val nativeAssetFee = extrinsicService.estimateFeeV2(chain) { - executeSwap(args, origin = chain.emptyAccountId()) + val nativeAssetFee = extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { + executeSwap(args, sendTo = chain.emptyAccountId()) } return convertNativeFeeToPayingTokenFee(nativeAssetFee, args) } override suspend fun swap(args: SwapExecuteArgs): Result { - return extrinsicService.submitExtrinsicWithSelectedWalletV2(chain) { origin -> - executeSwap(args, origin) + return extrinsicService.submitExtrinsic(chain, TransactionOrigin.SelectedWallet) { submissionOrigin -> + // Send swapped funds to the requested origin since it the account doing the swap + executeSwap(args, sendTo = submissionOrigin.requestedOrigin) } } @@ -211,7 +213,7 @@ private class AssetConversionExchange( return requireNotNull(quotedAmount) } - private suspend fun ExtrinsicBuilder.executeSwap(swapExecuteArgs: SwapExecuteArgs, origin: AccountId) { + private suspend fun ExtrinsicBuilder.executeSwap(swapExecuteArgs: SwapExecuteArgs, sendTo: AccountId) { val path = listOf(swapExecuteArgs.assetIn, swapExecuteArgs.assetOut) .map { asset -> multiLocationConverter.encodableMultiLocationOf(asset) } @@ -225,7 +227,7 @@ private class AssetConversionExchange( "path" to path, "amount_in" to swapLimit.expectedAmountIn, "amount_out_min" to swapLimit.amountOutMin, - "send_to" to origin, + "send_to" to sendTo, "keep_alive" to keepAlive ) ) @@ -237,7 +239,7 @@ private class AssetConversionExchange( "path" to path, "amount_out" to swapLimit.expectedAmountOut, "amount_in_max" to swapLimit.amountInMax, - "send_to" to origin, + "send_to" to sendTo, "keep_alive" to keepAlive ) ) diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/repository/SwapTransactionHistoryRepository.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/repository/SwapTransactionHistoryRepository.kt index dd6e420db6..cc1afc6a07 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/repository/SwapTransactionHistoryRepository.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/repository/SwapTransactionHistoryRepository.kt @@ -40,7 +40,7 @@ class RealSwapTransactionHistoryRepository( val localOperation = with(swapArgs) { OperationLocal.manualSwap( hash = txSubmission.hash, - originAddress = chain.addressOf(txSubmission.origin), + originAddress = chain.addressOf(txSubmission.submissionOrigin.requestedOrigin), assetId = chainAsset.localId, fee = feeAsset.withAmountLocal(fee.networkFee.amount), amountIn = assetIn.withAmountLocal(swapLimit.expectedAmountIn), diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/cache/AssetCache.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/cache/AssetCache.kt index 4238d7c53d..bf50ae6e2b 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/cache/AssetCache.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/cache/AssetCache.kt @@ -56,7 +56,7 @@ class AssetCache( chainAsset: Chain.Asset, builder: (local: AssetLocal) -> AssetLocal, ): Boolean = withContext(Dispatchers.IO) { - val applicableMetaAccount = accountRepository.findMetaAccount(accountId) + val applicableMetaAccount = accountRepository.findMetaAccount(accountId, chainAsset.chainId) applicableMetaAccount?.let { updateAsset(it.id, chainAsset, builder) diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/tranfers/AssetTransfers.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/tranfers/AssetTransfers.kt index b1b8f9407b..31af3e14bd 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/tranfers/AssetTransfers.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/tranfers/AssetTransfers.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_wallet_api.domain.model.Token @@ -7,9 +8,9 @@ import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.runtime.ext.accountIdOrNull import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.AccountId -import java.math.BigDecimal import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf +import java.math.BigDecimal interface AssetTransfer { val sender: MetaAccount @@ -71,7 +72,7 @@ interface AssetTransfers { suspend fun calculateFee(transfer: AssetTransfer): Fee - suspend fun performTransfer(transfer: WeightedAssetTransfer): Result + suspend fun performTransfer(transfer: WeightedAssetTransfer): Result suspend fun totalCanDropBelowMinimumBalance(chainAsset: Chain.Asset): Boolean { return true diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/crosschain/CrossChainTransactor.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/crosschain/CrossChainTransactor.kt index 7ade311ae8..5fa0be13ca 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/crosschain/CrossChainTransactor.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/crosschain/CrossChainTransactor.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_api.data.network.crosschain +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfer import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfersValidationSystem @@ -19,5 +20,5 @@ interface CrossChainTransactor { configuration: CrossChainTransferConfiguration, transfer: AssetTransfer, crossChainFee: BigInteger, - ): Result<*> + ): Result } diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt index 8efbb44892..d894dfb7df 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.transform import java.math.BigDecimal -import java.math.BigInteger sealed class FeeStatus { object Loading : FeeStatus() @@ -98,13 +97,6 @@ interface FeeLoaderMixin : GenericFeeLoaderMixin { // Additional methods in this interface are only for backward-compatibility to simplify migration of the old code interface Presentation : GenericFeeLoaderMixin.Presentation, FeeLoaderMixin { - @Deprecated("Use loadFeeV2") - fun loadFee( - coroutineScope: CoroutineScope, - feeConstructor: suspend (Token) -> BigInteger?, - onRetryCancelled: () -> Unit, - ) - @Deprecated("Use setFee(fee: GenericFee)") suspend fun setFee(feeAmount: BigDecimal?) @@ -116,7 +108,7 @@ interface FeeLoaderMixin : GenericFeeLoaderMixin { suspend fun setFee(fee: Fee?) = setFee(fee?.let(::SimpleFee)) - fun loadFeeV2( + fun loadFee( coroutineScope: CoroutineScope, expectedChain: ChainId? = null, feeConstructor: suspend (Token) -> Fee?, @@ -183,44 +175,6 @@ fun FeeLoaderMixin.Presentation.requireFee( } } -fun FeeLoaderMixin.Presentation.connectWith( - inputSource: Flow, - scope: CoroutineScope, - feeConstructor: suspend Token.(input: I) -> BigInteger, - onRetryCancelled: () -> Unit = {} -) { - inputSource.onEach { input -> - loadFee( - coroutineScope = scope, - feeConstructor = { feeConstructor(it, input) }, - onRetryCancelled = onRetryCancelled - ) - } - .inBackground() - .launchIn(scope) -} - -fun FeeLoaderMixin.Presentation.connectWith( - inputSource1: Flow, - inputSource2: Flow, - scope: CoroutineScope, - feeConstructor: suspend Token.(input1: I1, input2: I2) -> BigInteger?, - onRetryCancelled: () -> Unit = {} -) { - combine( - inputSource1, - inputSource2 - ) { input1, input2 -> - loadFee( - coroutineScope = scope, - feeConstructor = { feeConstructor(it, input1, input2) }, - onRetryCancelled = onRetryCancelled - ) - } - .inBackground() - .launchIn(scope) -} - fun FeeLoaderMixin.Presentation.connectWith( inputSource1: Flow, inputSource2: Flow, @@ -237,7 +191,7 @@ fun FeeLoaderMixin.Presentation.connectWith( inputSource3, inputSource4 ) { input1, input2, input3, input4 -> - loadFeeV2( + loadFee( coroutineScope = scope, expectedChain = expectedChain?.invoke(input1, input2, input3, input4), feeConstructor = { feeConstructor(it, input1, input2, input3, input4) }, @@ -248,14 +202,14 @@ fun FeeLoaderMixin.Presentation.connectWith( .launchIn(scope) } -fun FeeLoaderMixin.Presentation.connectWithV2( +fun FeeLoaderMixin.Presentation.connectWith( inputSource: Flow, scope: CoroutineScope, feeConstructor: suspend Token.(input: I) -> Fee, onRetryCancelled: () -> Unit = {} ) { inputSource.onEach { input -> - loadFeeV2( + this.loadFee( coroutineScope = scope, feeConstructor = { feeConstructor(it, input) }, onRetryCancelled = onRetryCancelled @@ -265,7 +219,7 @@ fun FeeLoaderMixin.Presentation.connectWithV2( .launchIn(scope) } -fun FeeLoaderMixin.Presentation.connectWithV2( +fun FeeLoaderMixin.Presentation.connectWith( inputSource1: Flow, inputSource2: Flow, scope: CoroutineScope, @@ -276,7 +230,7 @@ fun FeeLoaderMixin.Presentation.connectWithV2( inputSource1, inputSource2 ) { input1, input2 -> - loadFeeV2( + this.loadFee( coroutineScope = scope, feeConstructor = { feeConstructor(it, input1, input2) }, onRetryCancelled = onRetryCancelled diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/GenericFeeLoaderProvider.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/GenericFeeLoaderProvider.kt index 1ce1bbd366..5a6f8fdb51 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/GenericFeeLoaderProvider.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/GenericFeeLoaderProvider.kt @@ -20,7 +20,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal -import java.math.BigInteger class FeeLoaderProviderFactory( private val resourceManager: ResourceManager, @@ -47,20 +46,6 @@ private class FeeLoaderProvider( tokenFlow: Flow, ) : GenericFeeLoaderProvider(resourceManager, configuration, tokenFlow), FeeLoaderMixin.Presentation { - override fun loadFee( - coroutineScope: CoroutineScope, - feeConstructor: suspend (Token) -> BigInteger?, - onRetryCancelled: () -> Unit, - ) { - coroutineScope.launch { - loadFeeSuspending( - retryScope = coroutineScope, - feeConstructor = { token -> feeConstructor(token)?.let { SimpleFee(InlineFee(it)) } }, - onRetryCancelled = onRetryCancelled - ) - } - } - override suspend fun setFee(feeAmount: BigDecimal?) { val fee = feeAmount?.let { val token = tokenFlow.firstNotNull() diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/BaseAssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/BaseAssetTransfers.kt index 4e8bede01d..a0b9628eb9 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/BaseAssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/BaseAssetTransfers.kt @@ -1,9 +1,11 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.transfers import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.intoOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfer import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfers @@ -47,16 +49,14 @@ abstract class BaseAssetTransfers( */ protected abstract suspend fun transferFunctions(chainAsset: Chain.Asset): List> - override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { - val senderAccountId = transfer.sender.accountIdIn(transfer.originChain)!! - - return extrinsicService.submitExtrinsicWithAnySuitableWallet(transfer.originChain, senderAccountId) { + override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { + return extrinsicService.submitExtrinsic(transfer.originChain, transfer.sender.intoOrigin()) { transfer(transfer) } } override suspend fun calculateFee(transfer: AssetTransfer): Fee { - return extrinsicService.estimateFeeV2(transfer.originChain) { + return extrinsicService.estimateFee(transfer.originChain, TransactionOrigin.SelectedWallet) { transfer(transfer) } } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/UnsupportedAssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/UnsupportedAssetTransfers.kt index 989a07a99b..e2d8cc810c 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/UnsupportedAssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/UnsupportedAssetTransfers.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.transfers +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfer import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfers @@ -16,7 +17,7 @@ open class UnsupportedAssetTransfers : AssetTransfers { throw UnsupportedOperationException("Unsupported") } - override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { + override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { return Result.failure(UnsupportedOperationException("Unsupported")) } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmErc20/EvmErc20AssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmErc20/EvmErc20AssetTransfers.kt index 532b5143f0..e0ac0dfaee 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmErc20/EvmErc20AssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmErc20/EvmErc20AssetTransfers.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.asset import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfer @@ -52,7 +53,7 @@ class EvmErc20AssetTransfers( } } - override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { + override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { return evmTransactionService.transact( chainId = transfer.originChain.id, presetFee = transfer.decimalFee.fee, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmNative/EvmNativeAssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmNative/EvmNativeAssetTransfers.kt index 52d98388a5..ce7ab5f36b 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmNative/EvmNativeAssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmNative/EvmNativeAssetTransfers.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.asset import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfer @@ -48,7 +49,7 @@ class EvmNativeAssetTransfers( } } - override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { + override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { return evmTransactionService.transact( chainId = transfer.originChain.id, fallbackGasLimit = NATIVE_COIN_TRANSFER_GAS_LIMIT, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainTransactor.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainTransactor.kt index d181775c4e..e83fa91991 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainTransactor.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainTransactor.kt @@ -5,7 +5,9 @@ import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.common.utils.xTokensName import io.novafoundation.nova.common.utils.xcmPalletName import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfer @@ -68,7 +70,7 @@ class RealCrossChainTransactor( } override suspend fun estimateOriginFee(configuration: CrossChainTransferConfiguration, transfer: AssetTransfer): Fee { - return extrinsicService.estimateFeeV2(transfer.originChain) { + return extrinsicService.estimateFee(transfer.originChain, TransactionOrigin.SelectedWallet) { crossChainTransfer(configuration, transfer, crossChainFee = Balance.ZERO) } } @@ -77,8 +79,8 @@ class RealCrossChainTransactor( configuration: CrossChainTransferConfiguration, transfer: AssetTransfer, crossChainFee: BigInteger - ): Result<*> { - return extrinsicService.submitExtrinsicWithSelectedWallet(transfer.originChain) { + ): Result { + return extrinsicService.submitExtrinsic(transfer.originChain, TransactionOrigin.SelectedWallet) { crossChainTransfer(configuration, transfer, crossChainFee) } } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainWeigher.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainWeigher.kt index 3f52e0124e..ffb72de47f 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainWeigher.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainWeigher.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.crosschain import io.novafoundation.nova.common.data.network.runtime.binding.Weight import io.novafoundation.nova.common.utils.orZero +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.data.network.crosschain.CrossChainFee @@ -57,7 +58,7 @@ class RealCrossChainWeigher( Mode.Standard -> { val xcmMessage = xcmMessage(feeConfig.xcmFeeType.instructions, chain) - val paymentInfo = extrinsicService.paymentInfo(chain) { + val paymentInfo = extrinsicService.paymentInfo(chain, TransactionOrigin.SelectedWallet) { xcmExecute(xcmMessage, maxWeight) } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/repository/WalletRepositoryImpl.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/repository/WalletRepositoryImpl.kt index 6b50f2e8c7..007f4c1dfe 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/repository/WalletRepositoryImpl.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/repository/WalletRepositoryImpl.kt @@ -132,7 +132,7 @@ class WalletRepositoryImpl( override fun assetFlow(accountId: AccountId, chainAsset: Chain.Asset): Flow { return flow { - val metaAccount = accountRepository.findMetaAccountOrThrow(accountId) + val metaAccount = accountRepository.findMetaAccountOrThrow(accountId, chainAsset.chainId) emitAll(assetFlow(metaAccount.id, chainAsset)) } @@ -237,7 +237,7 @@ class WalletRepositoryImpl( } private suspend fun getAsset(accountId: AccountId, chainId: String, assetId: Int) = withContext(Dispatchers.Default) { - val metaAccount = accountRepository.findMetaAccountOrThrow(accountId) + val metaAccount = accountRepository.findMetaAccountOrThrow(accountId, chainId) assetCache.getAssetWithToken(metaAccount.id, chainId, assetId) } From b5596e7bfaf5852aaa523f092562bcec7d23f199 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Wed, 20 Dec 2023 14:08:24 +0300 Subject: [PATCH 054/100] Fix - send title is not visible until xcm config is synced (#1281) --- .../presentation/send/amount/SelectSendViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt index 4c96ebc202..aa536d0f8c 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt @@ -59,6 +59,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal @@ -113,6 +114,7 @@ class SelectSendViewModel( } private val availableCrossChainDestinations = availableCrossChainDestinations() + .onStart { emit(emptyList()) } .shareInBackground() val isSelectAddressAvailable = combine(originChain, destinationChain) { originChain, destinationChain -> From aefb25a45843988a0ee1049634a32ac4a2599f4a Mon Sep 17 00:00:00 2001 From: Valentun Date: Wed, 20 Dec 2023 14:08:53 +0300 Subject: [PATCH 055/100] Code style --- .../data/ethereum/transaction/TransactionOrigin.kt | 2 +- .../feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt | 1 - .../feature_account_api/domain/interfaces/AccountRepository.kt | 2 +- .../feature_account_impl/data/extrinsic/RealExtrinsicService.kt | 1 - .../feature_staking_api/domain/model/relaychain/StakingState.kt | 1 - 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt index 1c6f5040a0..bf22ceebdf 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/ethereum/transaction/TransactionOrigin.kt @@ -9,7 +9,7 @@ sealed class TransactionOrigin { class WalletWithAccount(val accountId: AccountId) : TransactionOrigin() - class Wallet(val metaAccount: MetaAccount): TransactionOrigin() + class Wallet(val metaAccount: MetaAccount) : TransactionOrigin() } fun AccountId.intoOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(this) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt index 44a8bfa259..9a7b95da70 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicServiceExt.kt @@ -1,7 +1,6 @@ package io.novafoundation.nova.feature_account_api.data.extrinsic import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index 4d9317135d..eb13abb1d5 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -122,5 +122,5 @@ interface AccountRepository { password: String ): String - suspend fun isAccountExists(accountId: AccountId, chainId: ChainId): Boolean + suspend fun isAccountExists(accountId: AccountId, chainId: ChainId): Boolean } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index ccd8d9ee83..a4c1d1eaff 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -38,7 +38,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first - class RealExtrinsicService( private val rpcCalls: RpcCalls, private val chainRegistry: ChainRegistry, diff --git a/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/relaychain/StakingState.kt b/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/relaychain/StakingState.kt index b65e4f059e..2a89071eb4 100644 --- a/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/relaychain/StakingState.kt +++ b/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/model/relaychain/StakingState.kt @@ -55,7 +55,6 @@ sealed class StakingState( } } - fun StakingState.Stash.stashTransactionOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(stashId) fun StakingState.Stash.controllerTransactionOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(controllerId) From cf8e1c29a7299563cae6523f8955193da597d463 Mon Sep 17 00:00:00 2001 From: Valentun Date: Wed, 20 Dec 2023 15:20:18 +0300 Subject: [PATCH 056/100] Fixes --- .../nova/feature_account_api/data/extrinsic/ExtrinsicService.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt index 431c7bd9ed..8e2267f81a 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt @@ -17,8 +17,6 @@ typealias FormExtrinsicWithOrigin = suspend ExtrinsicBuilder.(origin: Submission typealias FormMultiExtrinsicWithOrigin = suspend CallBuilder.(origin: SubmissionOrigin) -> Unit typealias FormMultiExtrinsic = suspend CallBuilder.() -> Unit -typealias ExtrinsicHash = String - class SubmissionOrigin( /** * Origin that was originally requested to sign the transaction From 68890aa17644e1c9e965ac6c966e454f3c699e81 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 20 Dec 2023 14:37:31 +0100 Subject: [PATCH 057/100] Fixed proxied deactivated state --- .../nova/common/utils/AlphaColorFilter.kt | 12 ++++ .../nova/common/utils/AlphaDrawable.kt | 55 +++++++++++++++++++ .../account/listing/AccountListAdapter.kt | 3 + .../account/listing/CommonAccountsAdapter.kt | 4 +- .../account/listing/holders/AccountHolder.kt | 8 ++- .../account/listing/items/AccountUi.kt | 6 +- .../transaction/RealEvmTransactionService.kt | 1 - ...DelegatedMetaAccountUpdatesListingMixin.kt | 28 ++++++++-- ...aAccountValidForTransactionListingMixin.kt | 5 +- .../MetaAccountWithBalanceListingMixin.kt | 11 ++-- .../account/common/listing/ProxyFormatter.kt | 13 +++-- .../account/list/WalletListFragment.kt | 2 +- .../DelegatedAccountsAdapter.kt | 1 + .../management/WalletManagmentFragment.kt | 7 ++- .../SelectAddressLedgerFragment.kt | 9 ++- .../SelectAddressLedgerViewModel.kt | 5 +- .../source/query/BaseStorageQueryContext.kt | 1 - 17 files changed, 139 insertions(+), 32 deletions(-) create mode 100644 common/src/main/java/io/novafoundation/nova/common/utils/AlphaColorFilter.kt create mode 100644 common/src/main/java/io/novafoundation/nova/common/utils/AlphaDrawable.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/AlphaColorFilter.kt b/common/src/main/java/io/novafoundation/nova/common/utils/AlphaColorFilter.kt new file mode 100644 index 0000000000..06a57484e6 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/AlphaColorFilter.kt @@ -0,0 +1,12 @@ +package io.novafoundation.nova.common.utils + +import android.graphics.ColorMatrixColorFilter + +class AlphaColorFilter(val alpha: Float) : ColorMatrixColorFilter( + floatArrayOf( + 1f, 0f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, 0f, + 0f, 0f, 1f, 0f, 0f, + 0f, 0f, 0f, alpha, 0f + ) +) diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/AlphaDrawable.kt b/common/src/main/java/io/novafoundation/nova/common/utils/AlphaDrawable.kt new file mode 100644 index 0000000000..f34c63b91e --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/AlphaDrawable.kt @@ -0,0 +1,55 @@ +package io.novafoundation.nova.common.utils + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.drawable.Drawable +import androidx.core.graphics.minus +import androidx.core.graphics.toRectF + +/** + * Note: this implementation is very expensive (see [Canvas.saveLayerAlpha]). + * This is usefull for drawables without implemented alpha and color filter support, such as PictureDrawable. + * In other cases it's recommended to use [Drawable.setAlpha] or [Drawable.setColorFilter] instead of this class. + */ +class AlphaDrawable(private val nestedDrawable: Drawable, private var alpha: Float) : Drawable() { + + private val layerBounds: RectF = RectF() + + init { + bounds = Rect(nestedDrawable.bounds) + layerBounds.set(bounds.toRectF()) + } + + override fun draw(canvas: Canvas) { + canvas.saveLayerAlpha(layerBounds, (alpha * 255).toInt()) + nestedDrawable.draw(canvas) + canvas.restore() + } + + override fun setAlpha(alpha: Int) { + this.alpha = alpha.toFloat() / 255f + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + nestedDrawable.colorFilter = colorFilter + } + + @Deprecated("Deprecated in Java") + override fun getOpacity(): Int { + return nestedDrawable.opacity + } + + override fun getIntrinsicWidth(): Int { + return nestedDrawable.intrinsicWidth + } + + override fun getIntrinsicHeight(): Int { + return nestedDrawable.intrinsicHeight + } +} + +fun Drawable.withAlphaDrawable(alpha: Float): Drawable { + return AlphaDrawable(this, alpha) +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt index 04c9c45e90..a7b3f6c222 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/AccountListAdapter.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_account_api.presenatation.account.listing +import androidx.annotation.ColorRes import coil.ImageLoader import io.novafoundation.nova.common.view.ChipLabelView import io.novafoundation.nova.feature_account_api.presenatation.account.listing.holders.AccountHolder @@ -9,6 +10,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.listing. class AccountsAdapter( private val accountItemHandler: AccountHolder.AccountItemHandler, private val imageLoader: ImageLoader, + @ColorRes private val chainBorderColor: Int, initialMode: AccountHolder.Mode ) : CommonAccountsAdapter( accountItemHandler = accountItemHandler, @@ -16,5 +18,6 @@ class AccountsAdapter( diffCallback = AccountDiffCallback(AccountChipGroupRvItem::class.java), groupFactory = { AccountChipHolder(ChipLabelView(it.context)) }, groupBinder = { holder, item -> (holder as AccountChipHolder).bind(item) }, + chainBorderColor = chainBorderColor, initialMode = initialMode ) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/CommonAccountsAdapter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/CommonAccountsAdapter.kt index 97393df2cd..2c97d98b32 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/CommonAccountsAdapter.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/CommonAccountsAdapter.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_api.presenatation.account.listing import android.view.ViewGroup +import androidx.annotation.ColorRes import coil.ImageLoader import io.novafoundation.nova.common.list.BaseGroupedDiffCallback import io.novafoundation.nova.common.list.GroupedListAdapter @@ -29,6 +30,7 @@ abstract class CommonAccountsAdapter( private val diffCallback: AccountDiffCallback, private val groupFactory: AccountGroupViewHolderFactory, private val groupBinder: AccountGroupViewHolderBinder, + @ColorRes private val chainBorderColor: Int, initialMode: AccountHolder.Mode, ) : GroupedListAdapter(diffCallback) { @@ -45,7 +47,7 @@ abstract class CommonAccountsAdapter( } override fun createChildViewHolder(parent: ViewGroup): GroupedListHolder { - return AccountHolder(parent.inflateChild(R.layout.item_account), imageLoader) + return AccountHolder(parent.inflateChild(R.layout.item_account), imageLoader, chainBorderColor) } override fun bindGroup(holder: GroupedListHolder, group: Group) { diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt index 65f0abcbe8..7dc36190d8 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt @@ -2,9 +2,11 @@ package io.novafoundation.nova.feature_account_api.presenatation.account.listing import android.animation.LayoutTransition import android.view.View +import androidx.annotation.ColorRes import androidx.core.view.isVisible import coil.ImageLoader import io.novafoundation.nova.common.list.GroupedListHolder +import io.novafoundation.nova.common.utils.AlphaColorFilter import io.novafoundation.nova.common.utils.letOrHide import io.novafoundation.nova.common.utils.setDrawableEnd import io.novafoundation.nova.common.utils.setDrawableStart @@ -20,7 +22,7 @@ import kotlinx.android.synthetic.main.item_account.view.itemAccountSubtitle import kotlinx.android.synthetic.main.item_account.view.itemAccountTitle import kotlinx.android.synthetic.main.item_account.view.itemChainIcon -class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedListHolder(view) { +class AccountHolder(view: View, private val imageLoader: ImageLoader, @ColorRes private val chainBorderColor: Int) : GroupedListHolder(view) { interface AccountItemHandler { @@ -42,6 +44,7 @@ class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedL } containerView.itemAccountContainer.layoutTransition = lt + containerView.itemChainIcon.backgroundTintList = containerView.context.getColorStateList(chainBorderColor) } fun bind( @@ -55,6 +58,7 @@ class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedL itemAccountIcon.setImageDrawable(accountModel.picture) itemChainIcon.letOrHide(accountModel.chainIconUrl) { + itemChainIcon.colorFilter = AlphaColorFilter(accountModel.chainIconOpacity) itemChainIcon.loadChainIcon(it, imageLoader = imageLoader) } @@ -63,8 +67,6 @@ class AccountHolder(view: View, private val imageLoader: ImageLoader) : GroupedL } else { itemAccountTitle.setDrawableEnd(null) } - - itemAccountContainer.alpha = if (accountModel.enabled) 1f else 0.56f } fun bindName(accountModel: AccountUi) { diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt index 3c13b4bb06..4a260ee54d 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt @@ -4,13 +4,13 @@ import android.graphics.drawable.Drawable class AccountUi( val id: Long, - val title: String, + val title: CharSequence, val subtitle: CharSequence, val isSelected: Boolean, val isClickable: Boolean, val picture: Drawable, val chainIconUrl: String?, - val enabled: Boolean, val updateIndicator: Boolean, - val subtitleIconRes: Int? + val subtitleIconRes: Int?, + val chainIconOpacity: Float = 1f ) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index 45473e99ce..28a556bd9e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -23,7 +23,6 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw import org.web3j.crypto.RawTransaction diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt index 52e7c85aba..ef140a8589 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt @@ -3,6 +3,9 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.common. import io.novafoundation.nova.common.list.toListWithHeaders import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.WithCoroutineScopeExtensions +import io.novafoundation.nova.common.utils.colorSpan +import io.novafoundation.nova.common.utils.toSpannable +import io.novafoundation.nova.common.utils.withAlphaDrawable import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxiedAndProxyMetaAccount @@ -32,6 +35,8 @@ class DelegatedMetaAccountUpdatesListingMixinFactory( } } +private const val ICON_ALPHA = 0.56f + private class DelegatedMetaAccountUpdatesListingMixin( private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, private val walletUiUseCase: WalletUiUseCase, @@ -59,25 +64,36 @@ private class DelegatedMetaAccountUpdatesListingMixin( } private suspend fun mapProxiedToUi(proxiedWithProxy: ProxiedAndProxyMetaAccount) = with(proxiedWithProxy) { + val isEnabled = proxiedWithProxy.proxied.status == LightMetaAccount.Status.ACTIVE + val secondaryColor = resourceManager.getColor(R.color.text_secondary) + val title = proxied.name + val subtitle = mapSubtitle(this, isEnabled) + val walletIcon = walletUiUseCase.walletIcon(proxied) AccountUi( id = proxied.id, - title = proxied.name, - subtitle = mapSubtitle(this), + title = if (isEnabled) title else title.toSpannable(colorSpan(secondaryColor)), + subtitle = if (isEnabled) subtitle else subtitle.toSpannable(colorSpan(secondaryColor)), isSelected = false, isClickable = true, - picture = walletUiUseCase.walletIcon(proxied), + picture = if (isEnabled) walletIcon else walletIcon.withAlphaDrawable(ICON_ALPHA), chainIconUrl = proxiedWithProxy.chain.icon, + chainIconOpacity = ICON_ALPHA, subtitleIconRes = null, - enabled = proxiedWithProxy.proxied.status == LightMetaAccount.Status.ACTIVE, updateIndicator = false ) } private suspend fun mapSubtitle( - proxiedWithProxy: ProxiedAndProxyMetaAccount + proxiedWithProxy: ProxiedAndProxyMetaAccount, + isEnabled: Boolean ): CharSequence { val proxy = proxiedWithProxy.proxied.proxy ?: return proxiedWithProxy.proxiedAddress() // fallback - return proxyFormatter.mapProxyMetaAccountSubtitle(proxiedWithProxy.proxy, proxy) + val proxyIcon = proxyFormatter.makeAccountDrawable(proxiedWithProxy.proxy) + return proxyFormatter.mapProxyMetaAccountSubtitle( + proxiedWithProxy.proxy.name, + if (isEnabled) proxyIcon else proxyIcon.withAlphaDrawable(ICON_ALPHA), + proxy + ) } private fun ProxiedAndProxyMetaAccount.proxiedAddress(): String { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt index 3e1bd9de5d..c74521b077 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt @@ -80,9 +80,8 @@ private class MetaAccountValidForTransactionListingMixin( isClickable = chainAddress != null, picture = icon, chainIconUrl = null, - subtitleIconRes = if (chainAddress == null) R.drawable.ic_warning_filled else null, - enabled = true, - updateIndicator = false + updateIndicator = false, + subtitleIconRes = if (chainAddress == null) R.drawable.ic_warning_filled else null ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt index 2ac5858c29..326a863dd9 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt @@ -64,9 +64,8 @@ private class MetaAccountWithBalanceListingMixin( isClickable = true, picture = walletUiUseCase.walletIcon(metaAccount), chainIconUrl = proxyChain?.icon, - subtitleIconRes = null, - enabled = true, - updateIndicator = hasUpdates + updateIndicator = hasUpdates, + subtitleIconRes = null ) } @@ -90,7 +89,11 @@ private class MetaAccountWithBalanceListingMixin( val proxy = metaAccount.proxy ?: return formattedTotalBalance() val proxyMetaAccount = proxyMetaAccount ?: return formattedTotalBalance() - return proxyFormatter.mapProxyMetaAccountSubtitle(proxyMetaAccount, proxy) + return proxyFormatter.mapProxyMetaAccountSubtitle( + proxyMetaAccount.name, + proxyFormatter.makeAccountDrawable(proxyMetaAccount), + proxy + ) } private fun MetaAccountWithTotalBalance.formattedTotalBalance(): String { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt index dab826b822..061bd9ad0a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.common.listing +import android.graphics.drawable.Drawable import android.text.SpannableStringBuilder import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.append @@ -20,17 +21,17 @@ class ProxyFormatter( ) { suspend fun mapProxyMetaAccountSubtitle( - proxyMetaAccount: MetaAccount, + proxyAccountName: String, + proxyAccountIcon: Drawable, proxyAccount: ProxyAccount ): CharSequence { val proxyType = mapProxyTypeToString(resourceManager, proxyAccount.proxyType) - val accountIconDrawable = walletUiUseCase.walletIcon(proxyMetaAccount, 16) return SpannableStringBuilder(resourceManager.getString(R.string.proxy_wallet_subtitle, proxyType)) .appendSpace() - .appendEnd(drawableSpan(accountIconDrawable)) + .appendEnd(drawableSpan(proxyAccountIcon)) .appendSpace() - .append(proxyMetaAccount.name, colorSpan(resourceManager.getColor(R.color.text_primary))) + .append(proxyAccountName, colorSpan(resourceManager.getColor(R.color.text_primary))) } fun mapProxyTypeToString(resourceManager: ResourceManager, type: ProxyAccount.ProxyType): String { @@ -46,4 +47,8 @@ class ProxyFormatter( is ProxyAccount.ProxyType.Other -> type.name.splitCamelCase().joinToString { it.capitalize() } } } + + suspend fun makeAccountDrawable(metaAccount: MetaAccount): Drawable { + return walletUiUseCase.walletIcon(metaAccount, 16) + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt index 5ec041d6af..2f47fea984 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/WalletListFragment.kt @@ -25,7 +25,7 @@ abstract class WalletListFragment : lateinit var imageLoader: ImageLoader private val adapter by lazy(LazyThreadSafetyMode.NONE) { - AccountsAdapter(this, imageLoader, initialMode = viewModel.mode) + AccountsAdapter(this, imageLoader, initialMode = viewModel.mode, chainBorderColor = R.color.bottom_sheet_background) } override fun onCreateView( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountsAdapter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountsAdapter.kt index 468e97da9f..e83959240d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountsAdapter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountsAdapter.kt @@ -20,6 +20,7 @@ class DelegatedAccountsAdapter( diffCallback = AccountDiffCallback(AccountTitleGroupRvItem::class.java), groupFactory = DelegatedAccountsGroupFactory(), groupBinder = { holder, item -> (holder as AccountTitleHolder).bind(item) }, + chainBorderColor = R.color.bottom_sheet_background, initialMode = AccountHolder.Mode.VIEW ) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt index d740726cd9..6d5b22fc2b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/management/WalletManagmentFragment.kt @@ -32,7 +32,12 @@ class WalletManagmentFragment : BaseFragment(), Accoun ) = layoutInflater.inflate(R.layout.fragment_accounts, container, false) override fun initViews() { - adapter = AccountsAdapter(this, imageLoader, initialMode = viewModel.mode.value) + adapter = AccountsAdapter( + this, + imageLoader, + initialMode = viewModel.mode.value, + chainBorderColor = R.color.secondary_screen_background + ) accountsList.setHasFixedSize(true) accountsList.adapter = adapter diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt index 7c3dabab74..ebaba4cb84 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerFragment.kt @@ -35,7 +35,14 @@ abstract class SelectAddressLedgerFragment : @Inject protected lateinit var imageLoader: ImageLoader - private val addressesAdapter by lazy(LazyThreadSafetyMode.NONE) { AccountsAdapter(this, imageLoader, AccountHolder.Mode.SELECT) } + private val addressesAdapter by lazy(LazyThreadSafetyMode.NONE) { + AccountsAdapter( + this, + imageLoader, + chainBorderColor = R.color.secondary_screen_background, + AccountHolder.Mode.SELECT + ) + } private val loadMoreAdapter = LedgerSelectAddressLoadMoreAdapter(handler = this, lifecycleOwner = this) @Inject diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt index 93f499d3f9..3e896c259a 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt @@ -158,9 +158,8 @@ abstract class SelectAddressLedgerViewModel( isClickable = true, picture = addressModel.image, chainIconUrl = null, - subtitleIconRes = null, - enabled = true, - updateIndicator = false + updateIndicator = false, + subtitleIconRes = null ) } } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt index 9e1c2ab84e..5be9f7d720 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt @@ -7,7 +7,6 @@ import io.novafoundation.nova.common.data.network.runtime.binding.fromHexOrIncom import io.novafoundation.nova.common.data.network.runtime.binding.incompatible import io.novafoundation.nova.common.utils.ComponentHolder import io.novafoundation.nova.common.utils.mapValuesNotNull -import io.novafoundation.nova.common.utils.splitKeyToComponents import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilderImpl From 289883f76df334b7ebf9508c8b9a7eef3e7a2c9d Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 20 Dec 2023 14:53:53 +0100 Subject: [PATCH 058/100] Removed Parity sugner wallet details mixin --- .../mixin/ParitySignerWalletDetailsMixin.kt | 60 ------------------- .../mixin/WalletDetailsMixinFactory.kt | 9 +-- 2 files changed, 1 insertion(+), 68 deletions(-) delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ParitySignerWalletDetailsMixin.kt diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ParitySignerWalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ParitySignerWalletDetailsMixin.kt deleted file mode 100644 index 68409a236b..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ParitySignerWalletDetailsMixin.kt +++ /dev/null @@ -1,60 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin - -import io.novafoundation.nova.common.list.GroupedList -import io.novafoundation.nova.common.list.headers.TextHeader -import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.flowOf -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.feature_account_api.domain.model.asPolkadotVaultVariantOrThrow -import io.novafoundation.nova.feature_account_api.presenatation.account.details.ChainAccountActionsSheet.AccountAction -import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider -import io.novafoundation.nova.feature_account_impl.domain.account.details.AccountInChain -import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor -import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.AccountFormatterFactory -import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.hasAccountComparator -import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.polkadotVaultAccountTypeAlert -import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.polkadotVaultTitle -import io.novafoundation.nova.feature_account_impl.presentation.account.details.mixin.common.withChainComparator -import io.novafoundation.nova.feature_account_impl.presentation.account.details.model.AccountTypeAlert -import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first - -class ParitySignerWalletDetailsMixin( - private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, - private val resourceManager: ResourceManager, - private val accountFormatterFactory: AccountFormatterFactory, - private val interactor: WalletDetailsInteractor, - metaAccount: MetaAccount -) : WalletDetailsMixin( - interactor, - metaAccount -) { - - private val accountFormatter = accountFormatterFactory.create( - accountTitleFormatter = { it.polkadotVaultTitle(resourceManager, metaAccount) } - ) - - override val availableAccountActions: Flow> = flowOf { emptySet() } - - override val typeAlert: Flow = flowOf { - val vaultVariant = metaAccount.type.asPolkadotVaultVariantOrThrow() - val variantConfig = polkadotVaultVariantConfigProvider.variantConfigFor(vaultVariant) - polkadotVaultAccountTypeAlert(vaultVariant, variantConfig, resourceManager) - } - - override suspend fun getChainProjections(): GroupedList { - return interactor.getChainProjections(metaAccount, interactor.getAllChains(), hasAccountComparator().withChainComparator()) - } - - override suspend fun mapAccountHeader(from: AccountInChain.From): TextHeader? { - return null - } - - override suspend fun mapAccount(accountInChain: AccountInChain): AccountInChainUi { - return accountFormatter.formatChainAccountProjection( - accountInChain, - availableAccountActions.first() - ) - } -} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixinFactory.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixinFactory.kt index 97bd54a673..5bbaf95061 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixinFactory.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/WalletDetailsMixinFactory.kt @@ -24,14 +24,7 @@ class WalletDetailsMixinFactory( Type.LEDGER -> LedgerWalletDetailsMixin(resourceManager, accountFormatterFactory, interactor, metaAccount) - Type.PARITY_SIGNER -> ParitySignerWalletDetailsMixin( - polkadotVaultVariantConfigProvider, - resourceManager, - accountFormatterFactory, - interactor, - metaAccount - ) - + Type.PARITY_SIGNER, Type.POLKADOT_VAULT -> PolkadotVaultWalletDetailsMixin( polkadotVaultVariantConfigProvider, resourceManager, From 3fec8ad3ab6d1384980dad022f331c06684beaea Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 20 Dec 2023 15:09:42 +0100 Subject: [PATCH 059/100] Fixed imports --- .../presentation/account/details/WalletDetailsFragment.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsFragment.kt index a9a818d3a5..b29dd29b5e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/WalletDetailsFragment.kt @@ -21,10 +21,10 @@ import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.AccountInChainUi import io.novafoundation.nova.feature_account_impl.presentation.common.chainAccounts.ChainAccountsAdapter import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.addAccountChooser.ui.setupAddAccountLauncher -import kotlinx.android.synthetic.main.fragment_account_details.accountDetailsChainAccounts -import kotlinx.android.synthetic.main.fragment_account_details.accountDetailsNameField -import kotlinx.android.synthetic.main.fragment_account_details.accountDetailsToolbar -import kotlinx.android.synthetic.main.fragment_account_details.accountDetailsTypeAlert +import kotlinx.android.synthetic.main.fragment_wallet_details.accountDetailsChainAccounts +import kotlinx.android.synthetic.main.fragment_wallet_details.accountDetailsNameField +import kotlinx.android.synthetic.main.fragment_wallet_details.accountDetailsToolbar +import kotlinx.android.synthetic.main.fragment_wallet_details.accountDetailsTypeAlert import kotlinx.coroutines.flow.first import javax.inject.Inject From 8b53c464fc28509a89b58de12ab8f8a51cc8d223 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 20 Dec 2023 15:40:36 +0100 Subject: [PATCH 060/100] Update ProxiedWalletDetailsMixin.kt --- .../account/details/mixin/ProxiedWalletDetailsMixin.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ProxiedWalletDetailsMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ProxiedWalletDetailsMixin.kt index 86739c2512..35f1ac1bfd 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ProxiedWalletDetailsMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/details/mixin/ProxiedWalletDetailsMixin.kt @@ -40,13 +40,14 @@ class ProxiedWalletDetailsMixin( val proxyAccount = metaAccount.proxy ?: return@flowOf null val proxyMetaAccount = interactor.getMetaAccount(proxyAccount.metaId) + val proxyAccountWithIcon = proxyFormatter.mapProxyMetaAccount(proxyMetaAccount.name, proxyFormatter.makeAccountDrawable(proxyMetaAccount)) AccountTypeAlert( style = AlertView.Style( backgroundColorRes = R.color.block_background, iconRes = R.drawable.ic_proxy ), message = resourceManager.getString(R.string.proxied_wallet_details_info_warning), - subMessage = SpannableStringBuilder(proxyFormatter.mapProxyMetaAccount(proxyMetaAccount)) + subMessage = SpannableStringBuilder(proxyAccountWithIcon) .appendSpace() .append(proxyFormatter.mapProxyTypeToString(proxyAccount.proxyType)) ) From 1a82112397b278dbc6892d13df276be92edd0470 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 21 Dec 2023 03:32:42 +0100 Subject: [PATCH 061/100] Add proxy fee validation --- .../nova/app/root/di/RootDependencies.kt | 6 ++ .../nova/app/root/di/RootFeatureModule.kt | 3 +- .../di/busHandler/RequestBusHandlerModule.kt | 42 +++++++++ .../app/root/presentation/RootViewModel.kt | 10 ++- .../presentation/di/RootActivityModule.kt | 7 +- .../CompoundRequestBusHandler.kt | 9 ++ ...oxyExtrinsicValidationRequestBusHandler.kt | 64 ++++++++++++++ .../requestBusHandler/RequestBusHandler.kt | 5 ++ .../nova/common/utils/bus/BaseRequestBus.kt | 24 ++++++ .../nova/common/utils/bus/RequestBus.kt | 26 ++++++ common/src/main/res/values/strings.xml | 3 +- .../nova/core_db/dao/MetaAccountDao.kt | 6 +- .../ProxiedExtrinsicValidationSystem.kt | 23 +++++ .../ProxyExtrinsicValidationRequestBus.kt | 14 +++ .../di/AccountFeatureApi.kt | 3 + .../account/proxy/ProxySigningPresenter.kt | 7 +- .../transaction/RealEvmTransactionService.kt | 1 - .../data/signer/proxy/ProxiedSigner.kt | 74 +++++++++------- .../ProxiedExtrinsicValidationSystem.kt | 31 ------- .../ProxyHaveEnoughFeeValidation.kt | 70 --------------- .../ProxyHaveEnoughFeeValidation2.kt | 77 ----------------- .../di/AccountFeatureComponent.kt | 2 - .../di/AccountFeatureDependencies.kt | 6 -- .../di/AccountFeatureHolder.kt | 2 - .../di/modules/signers/ProxiedSignerModule.kt | 57 +++---------- .../di/modules/signers/SignersModule.kt | 2 - .../proxy/sign/RealProxySigningPresenter.kt | 33 +------ .../feature_wallet_api/di/WalletFeatureApi.kt | 3 + .../ProxyHaveEnoughFeeValidation.kt | 85 +++++++++++++++++++ .../di/WalletFeatureModule.kt | 13 +++ .../source/query/BaseStorageQueryContext.kt | 1 - 31 files changed, 398 insertions(+), 311 deletions(-) create mode 100644 app/src/main/java/io/novafoundation/nova/app/root/di/busHandler/RequestBusHandlerModule.kt create mode 100644 app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/CompoundRequestBusHandler.kt create mode 100644 app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt create mode 100644 app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/RequestBusHandler.kt create mode 100644 common/src/main/java/io/novafoundation/nova/common/utils/bus/BaseRequestBus.kt create mode 100644 common/src/main/java/io/novafoundation/nova/common/utils/bus/RequestBus.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxiedExtrinsicValidationSystem.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxyExtrinsicValidationRequestBus.kt delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxiedExtrinsicValidationSystem.kt delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation.kt delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation2.kt create mode 100644 feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt index b4731f3937..5eea5fa6ed 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/RootDependencies.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.common.utils.systemCall.SystemCallExecutor import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem @@ -22,6 +23,7 @@ import io.novafoundation.nova.feature_governance_api.data.MutableGovernanceState import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -70,6 +72,10 @@ interface RootDependencies { fun encryptionDefaults(): EncryptionDefaults + fun proxyExtrinsicValidationRequestBus(): ProxyExtrinsicValidationRequestBus + + fun proxyHaveEnoughFeeValidationFactory(): ProxyHaveEnoughFeeValidationFactory + val systemCallExecutor: SystemCallExecutor val contextManager: ContextManager diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureModule.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureModule.kt index 8d0266f744..acdb0015f2 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/RootFeatureModule.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.app.root.di import dagger.Module import dagger.Provides +import io.novafoundation.nova.app.root.di.busHandler.RequestBusHandlerModule import io.novafoundation.nova.app.root.domain.RootInteractor import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService @@ -9,7 +10,7 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository -@Module +@Module(includes = [RequestBusHandlerModule::class]) class RootFeatureModule { @Provides diff --git a/app/src/main/java/io/novafoundation/nova/app/root/di/busHandler/RequestBusHandlerModule.kt b/app/src/main/java/io/novafoundation/nova/app/root/di/busHandler/RequestBusHandlerModule.kt new file mode 100644 index 0000000000..16bef9e385 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/di/busHandler/RequestBusHandlerModule.kt @@ -0,0 +1,42 @@ +package io.novafoundation.nova.app.root.di.busHandler + +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoSet +import io.novafoundation.nova.app.root.presentation.requestBusHandler.CompoundRequestBusHandler +import io.novafoundation.nova.app.root.presentation.requestBusHandler.ProxyExtrinsicValidationRequestBusHandler +import io.novafoundation.nova.app.root.presentation.requestBusHandler.RequestBusHandler +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.coroutines.RootScope +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus +import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory + +@Module +class RequestBusHandlerModule { + + @Provides + @FeatureScope + @IntoSet + fun provideProxyExtrinsicValidationRequestBusHandler( + scope: RootScope, + proxyProxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus, + proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory, + resourceManager: ResourceManager + ): RequestBusHandler { + return ProxyExtrinsicValidationRequestBusHandler( + scope, + proxyProxyExtrinsicValidationRequestBus, + proxyHaveEnoughFeeValidationFactory, + resourceManager + ) + } + + @Provides + @FeatureScope + fun provideCompoundRequestBusHandler( + handlers: Set<@JvmSuppressWildcards RequestBusHandler> + ): CompoundRequestBusHandler { + return CompoundRequestBusHandler(handlers) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt index 3cc78b52f5..415c28c63a 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.app.root.presentation.deepLinks.CallbackEvent import io.novafoundation.nova.app.root.presentation.deepLinks.DeepLinkHandler import io.novafoundation.nova.app.root.presentation.deepLinks.common.DeepLinkHandlingException import io.novafoundation.nova.app.root.presentation.deepLinks.common.formatDeepLinkHandlingException +import io.novafoundation.nova.app.root.presentation.requestBusHandler.CompoundRequestBusHandler import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.mixin.api.NetworkStateMixin import io.novafoundation.nova.common.mixin.api.NetworkStateUi @@ -44,7 +45,8 @@ class RootViewModel( private val walletConnectSessionsUseCase: WalletConnectSessionsUseCase, private val deepLinkHandler: DeepLinkHandler, private val automaticInteractionGate: AutomaticInteractionGate, - private val rootScope: RootScope + private val rootScope: RootScope, + private val compoundRequestBusHandler: CompoundRequestBusHandler ) : BaseViewModel(), NetworkStateUi by networkStateMixin { private var willBeClearedForLanguageChange = false @@ -71,6 +73,8 @@ class RootViewModel( updatePhishingAddresses() + obserBusEvents() + walletConnectService.onPairErrorLiveData.observeForever { showError(it.peekContent()) } @@ -78,6 +82,10 @@ class RootViewModel( subscribeDeepLinkCallback() } + private fun obserBusEvents() { + compoundRequestBusHandler.observe() + } + private fun subscribeDeepLinkCallback() { deepLinkHandler.callbackFlow .onEach { handleDeepLinkCallbackEvent(it) } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/RootActivityModule.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/RootActivityModule.kt index 5ac9495209..a86dc36655 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/RootActivityModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/di/RootActivityModule.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.app.root.domain.RootInteractor import io.novafoundation.nova.app.root.presentation.RootRouter import io.novafoundation.nova.app.root.presentation.RootViewModel import io.novafoundation.nova.app.root.presentation.deepLinks.RootDeepLinkHandler +import io.novafoundation.nova.app.root.presentation.requestBusHandler.CompoundRequestBusHandler import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.mixin.api.NetworkStateMixin @@ -52,7 +53,8 @@ class RootActivityModule { walletConnectSessionsUseCase: WalletConnectSessionsUseCase, deepLinkHandler: RootDeepLinkHandler, automaticInteractionGate: AutomaticInteractionGate, - rootScope: RootScope + rootScope: RootScope, + compoundRequestBusHandler: CompoundRequestBusHandler ): ViewModel { return RootViewModel( interactor, @@ -69,7 +71,8 @@ class RootActivityModule { walletConnectSessionsUseCase, deepLinkHandler, automaticInteractionGate, - rootScope + rootScope, + compoundRequestBusHandler ) } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/CompoundRequestBusHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/CompoundRequestBusHandler.kt new file mode 100644 index 0000000000..34e8ebf099 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/CompoundRequestBusHandler.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.app.root.presentation.requestBusHandler + +class CompoundRequestBusHandler( + private val handlers: Set +) : RequestBusHandler { + override fun observe() { + handlers.forEach { it.observe() } + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt new file mode 100644 index 0000000000..1dbf21d2e4 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt @@ -0,0 +1,64 @@ +package io.novafoundation.nova.app.root.presentation.requestBusHandler + +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.bus.observeBusEvent +import io.novafoundation.nova.common.utils.coroutines.RootScope +import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus.ValidationResponse +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationFailure +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationPayload +import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks +import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory +import io.novafoundation.nova.feature_wallet_api.domain.validation.proxyHasEnoughFeeValidation +import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount +import kotlinx.coroutines.flow.launchIn + +class ProxyExtrinsicValidationRequestBusHandler( + private val scope: RootScope, + private val proxyProxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus, + private val proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory, + private val resourceManager: ResourceManager +) : RequestBusHandler { + override fun observe() { + proxyProxyExtrinsicValidationRequestBus.observeEvent() + .observeBusEvent { request -> + val validationResult = createValidationSystem() + .validate(request.validationPayload) + ValidationResponse(validationResult) + }.launchIn(scope) + } + + private fun createValidationSystem(): ValidationSystem { + return ValidationSystem { + proxyHasEnoughFee() + } + } + + private fun ValidationSystemBuilder.proxyHasEnoughFee() { + proxyHasEnoughFeeValidation( + factory = proxyHaveEnoughFeeValidationFactory, + proxyAccountId = { it.proxyAccountId }, + call = { it.call }, + chainWithAsset = { it.chainWithAsset }, + proxyNotEnoughFee = { payload, availableBalance, fee -> + val errorMessage = buildNotEnoughFeeMessage(payload, availableBalance, fee) + ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee(errorMessage) + } + ) + } + + private fun buildNotEnoughFeeMessage(payload: ProxiedExtrinsicValidationPayload, availableBalance: Balance, fee: Fee): String { + val asset = payload.chainWithAsset.asset + return resourceManager.getString( + R.string.proxy_error_not_enough_to_pay_fee_message, + payload.proxyMetaAccount.name, + asset.amountFromPlanks(fee.amount).formatTokenAmount(asset), + asset.amountFromPlanks(availableBalance).formatTokenAmount(asset), + ) + } +} diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/RequestBusHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/RequestBusHandler.kt new file mode 100644 index 0000000000..a4a1c67564 --- /dev/null +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/RequestBusHandler.kt @@ -0,0 +1,5 @@ +package io.novafoundation.nova.app.root.presentation.requestBusHandler + +interface RequestBusHandler { + fun observe() +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/bus/BaseRequestBus.kt b/common/src/main/java/io/novafoundation/nova/common/utils/bus/BaseRequestBus.kt new file mode 100644 index 0000000000..8dd6201fa4 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/bus/BaseRequestBus.kt @@ -0,0 +1,24 @@ +package io.novafoundation.nova.common.utils.bus + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.runBlocking +import kotlin.coroutines.Continuation +import kotlin.coroutines.suspendCoroutine + +abstract class BaseRequestBus : RequestBus { + + private val eventFlow = MutableSharedFlow, T>>() + + override suspend fun handle(request: T): R { + return suspendCoroutine { continuation -> + runBlocking { + eventFlow.emit(continuation to request) + } + } + } + + override fun observeEvent(): Flow, T>> { + return eventFlow + } +} diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/bus/RequestBus.kt b/common/src/main/java/io/novafoundation/nova/common/utils/bus/RequestBus.kt new file mode 100644 index 0000000000..b6f1965ce9 --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/utils/bus/RequestBus.kt @@ -0,0 +1,26 @@ +package io.novafoundation.nova.common.utils.bus + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.onEach +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +interface RequestBus { + + interface Request + + interface Response + + suspend fun handle(request: T): R + + fun observeEvent(): Flow, T>> +} + +fun Flow, T>>.observeBusEvent( + action: suspend (T) -> R +): Flow, T>> { + return this.onEach { (continuation, request) -> + val response = action(request) + continuation.resume(response) + } +} diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index f57d700067..24718d371f 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,8 +1,7 @@ - Unknown error - Delegated account % doesn’t have enough balance to pay the network fee of %. Available balance to pay fee: % + Delegated account %s doesn’t have enough balance to pay the network fee of %s. Available balance to pay fee: %s Not enough tokens to pay the fee Do not show this again diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 0e107d1bad..8ca5196bde 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -189,7 +189,8 @@ interface MetaAccountDao { @Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)") suspend fun changeAccountsStatus(metaIds: List, status: MetaAccountLocal.Status) - @Query(""" + @Query( + """ WITH RECURSIVE Chain AS ( SELECT pa.proxyMetaId FROM proxy_accounts pa @@ -201,7 +202,8 @@ interface MetaAccountDao { ) SELECT MAX(proxyMetaId) as finalMetaId FROM Chain - """) + """ + ) fun getLastProxyAccountId(proxiedMetaId: Long): Long? } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxiedExtrinsicValidationSystem.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxiedExtrinsicValidationSystem.kt new file mode 100644 index 0000000000..40e409fe94 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxiedExtrinsicValidationSystem.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.feature_account_api.data.proxy.validation + +import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +typealias ProxiedExtrinsicValidationSystem = ValidationSystem +typealias ProxiedExtrinsicValidationSystemBuilder = ValidationSystemBuilder + +class ProxiedExtrinsicValidationPayload( + val proxyMetaAccount: MetaAccount, + val proxyAccountId: AccountId, + val chainWithAsset: ChainWithAsset, + val call: GenericCall.Instance +) + +sealed interface ProxiedExtrinsicValidationFailure { + + class ProxyNotEnoughFee(val errorMessage: String) : ProxiedExtrinsicValidationFailure +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxyExtrinsicValidationRequestBus.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxyExtrinsicValidationRequestBus.kt new file mode 100644 index 0000000000..8829b6f2bf --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxyExtrinsicValidationRequestBus.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_account_api.data.proxy.validation + +import io.novafoundation.nova.common.utils.bus.BaseRequestBus +import io.novafoundation.nova.common.utils.bus.RequestBus +import io.novafoundation.nova.common.validation.ValidationStatus +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus.Request +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus.ValidationResponse + +class ProxyExtrinsicValidationRequestBus : BaseRequestBus() { + + class Request(val validationPayload: ProxiedExtrinsicValidationPayload) : RequestBus.Request + + class ValidationResponse(val validationResult: Result>) : RequestBus.Response +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt index dbb5da7ff7..fad833fece 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt @@ -6,6 +6,7 @@ import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults @@ -55,6 +56,8 @@ interface AccountFeatureApi { fun encryptionDefaults(): EncryptionDefaults + fun proxyExtrinsicValidationRequestBus(): ProxyExtrinsicValidationRequestBus + val addressInputMixinFactory: AddressInputMixinFactory val walletUiUseCase: WalletUiUseCase diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt index f6f808a909..f2bf6dc47f 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt @@ -1,10 +1,7 @@ package io.novafoundation.nova.feature_account_api.presenatation.account.proxy -import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import java.math.BigInteger interface ProxySigningPresenter { @@ -14,7 +11,5 @@ interface ProxySigningPresenter { suspend fun signingIsNotSupported() - suspend fun notEnoughFee(proxyMetaAccount: MetaAccount, asset: Chain.Asset, availableBalanceToPayFee: BigInteger, fee: Fee) - - suspend fun unsupportedValidationError() + suspend fun notEnoughFee(errorMessage: String) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index 45473e99ce..28a556bd9e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -23,7 +23,6 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.getCallEthereumApiOrThrow -import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.extensions.toHexString import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw import org.web3j.crypto.RawTransaction 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 64ce55b344..4eb42fbb4b 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 @@ -5,18 +5,21 @@ import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 import io.novafoundation.nova.common.utils.chainId import io.novafoundation.nova.common.utils.toCallInstance import io.novafoundation.nova.common.validation.ValidationStatus +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType +import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationPayload -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationSystem +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationPayload +import io.novafoundation.nova.runtime.ext.commissionAsset import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.Signer @@ -29,7 +32,7 @@ class ProxiedSignerFactory( private val accountRepository: AccountRepository, private val proxySigningPresenter: ProxySigningPresenter, private val proxyRepository: ProxyRepository, - private val proxiedExtrinsicValidationSystem: ProxiedExtrinsicValidationSystem, + private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus ) { fun create(metaAccount: MetaAccount, signerProvider: SignerProvider): ProxiedSigner { @@ -40,7 +43,7 @@ class ProxiedSignerFactory( signerProvider, proxySigningPresenter, proxyRepository, - proxiedExtrinsicValidationSystem + proxyExtrinsicValidationEventBus ) } } @@ -52,21 +55,23 @@ class ProxiedSigner( private val signerProvider: SignerProvider, private val proxySigningPresenter: ProxySigningPresenter, private val proxyRepository: ProxyRepository, - private val proxiedExtrinsicValidationSystem: ProxiedExtrinsicValidationSystem, + private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus ) : Signer { override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { + val chain = chainRegistry.getChain(payloadExtrinsic.chainId) val proxyMetaAccount = getProxyMetaAccount() acknowledgeProxyOperation(proxyMetaAccount) + if (isRootProxiedSigner()) { + validateExtrinsic(payloadExtrinsic, chain) + } + val delegate = createDelegate(proxyMetaAccount) - val modifiedPayload = modifyPayload(proxyMetaAccount, payloadExtrinsic) + val modifiedPayload = modifyPayload(proxyMetaAccount, payloadExtrinsic, chain) val signedExtrinsic = delegate.signExtrinsic(modifiedPayload) - - validateExtrinsic(signedExtrinsic) - return signedExtrinsic } @@ -78,31 +83,36 @@ class ProxiedSigner( return signerProvider.signerFor(proxyMetaAccount) } - private suspend fun validateExtrinsic(signedExtrinsic: SignedExtrinsic) { - val validationResult = proxiedExtrinsicValidationSystem.validate( - ProxiedExtrinsicValidationPayload( - proxiedMetaAccount, - chainRegistry.getChain(signedExtrinsic.payload.chainId), - signedExtrinsic.payload.call.toCallInstance()!!.call - ) + private suspend fun validateExtrinsic(signedExtrinsic: SignerPayloadExtrinsic, chain: Chain) { + val proxyAccount = accountRepository.getLastProxyAccountFor(proxiedMetaAccount.id) ?: throw IllegalStateException("Proxy account is not found") + val proxyAccountId = proxyAccount.accountIdIn(chain) ?: throw IllegalStateException("Proxy account is not found") + + val callInstance = signedExtrinsic.call.toCallInstance() ?: signingNotSupported() + + val validationPayload = ProxiedExtrinsicValidationPayload( + proxyAccount, + proxyAccountId, + ChainWithAsset(chain, chain.commissionAsset), + callInstance.call ) - val validationStatus = validationResult.getOrNull() - if (validationStatus is ValidationStatus.NotValid) { - when (val reason = validationStatus.reason) { - is ProxyNotEnoughFee -> proxySigningPresenter.notEnoughFee(reason.proxyMetaAccount, reason.availableBalanceToPayFee, reason.fee) - else -> proxySigningPresenter.unsupportedValidationError() - } + val requestBusPayload = ProxyExtrinsicValidationRequestBus.Request(validationPayload) + val validationResponse = proxyExtrinsicValidationEventBus.handle(requestBusPayload) + + val validationStatus = validationResponse.validationResult.getOrNull() + if (validationStatus is ValidationStatus.NotValid && validationStatus.reason is ProxyNotEnoughFee) { + val reason = validationStatus.reason as ProxyNotEnoughFee + proxySigningPresenter.notEnoughFee(reason.errorMessage) throw SigningCancelledException() } } - private suspend fun modifyPayload(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic): SignerPayloadExtrinsic { + private suspend fun modifyPayload(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic, chain: Chain): SignerPayloadExtrinsic { val availableProxyTypes = proxyRepository.getDelegatedProxyTypes( payload.chainId, - proxiedMetaAccount.getAccountId(payload.chainId), - proxyMetaAccount.getAccountId(payload.chainId) + proxiedMetaAccount.getAccountId(chain), + proxyMetaAccount.getAccountId(chain) ) val callInstance = payload.call.toCallInstance() ?: signingNotSupported() @@ -111,7 +121,12 @@ class ProxiedSigner( .matchToProxyTypes(availableProxyTypes) ?: notEnoughPermission(proxyMetaAccount, availableProxyTypes) - return payload.wrapIntoProxyPayload(proxyMetaAccount.getAccountId(payload.chainId), proxyType, callInstance) + return payload.wrapIntoProxyPayload(proxyMetaAccount.getAccountId(chain), proxyType, callInstance) + } + + private suspend fun isRootProxiedSigner(): Boolean { + val selectedMetaAccount = accountRepository.getSelectedMetaAccount() + return selectedMetaAccount.id == proxiedMetaAccount.id } private suspend fun acknowledgeProxyOperation(proxyMetaAccount: MetaAccount) { @@ -121,8 +136,7 @@ class ProxiedSigner( } } - private suspend fun MetaAccount.getAccountId(chainId: ChainId): ByteArray { - val chain = chainRegistry.getChain(chainId) + private suspend fun MetaAccount.getAccountId(chain: Chain): ByteArray { return requireAccountIdIn(chain) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxiedExtrinsicValidationSystem.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxiedExtrinsicValidationSystem.kt deleted file mode 100644 index f85f2121df..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxiedExtrinsicValidationSystem.kt +++ /dev/null @@ -1,31 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation - -import io.novafoundation.nova.common.validation.ValidationSystem -import io.novafoundation.nova.common.validation.ValidationSystemBuilder -import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry -import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import java.math.BigInteger -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall - - -typealias ProxiedExtrinsicValidationSystem = ValidationSystem -typealias ProxiedExtrinsicValidationSystemBuilder = ValidationSystemBuilder - -class ProxiedExtrinsicValidationPayload(val rootProxiedMetaAccount: MetaAccount, val chain: Chain, val call: GenericCall.Instance) - -sealed interface ProxiedExtrinsicValidationFailure { - class ProxyNotEnoughFee(val proxyMetaAccount: MetaAccount, val availableBalanceToPayFee: BigInteger, val fee: Fee) : ProxiedExtrinsicValidationFailure - - object ProxyMetaAccountNotFound : ProxiedExtrinsicValidationFailure -} - -fun proxiedExtrinsicValidationSystem( - proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory -) = ValidationSystem { - proxyHaveEnoughFeeValidation(proxyHaveEnoughFeeValidationFactory) -} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation.kt deleted file mode 100644 index a7698b4c55..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation.kt +++ /dev/null @@ -1,70 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation - -import io.novafoundation.nova.common.utils.atLeastZero -import io.novafoundation.nova.common.validation.Validation -import io.novafoundation.nova.common.validation.ValidationStatus -import io.novafoundation.nova.common.validation.ValidationSystemBuilder -import io.novafoundation.nova.common.validation.validOrError -import io.novafoundation.nova.common.validation.validationError -import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyMetaAccountNotFound -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance -import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository -import io.novafoundation.nova.runtime.ext.commissionAsset -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall - -class ProxyHaveEnoughFeeValidationFactory( - private val assetSourceRegistry: AssetSourceRegistry, - private val accountRepository: AccountRepository, - private val walletRepository: WalletRepository, - private val extrinsicService: ExtrinsicService -) { - fun create(): ProxyHaveEnoughFeeValidation { - return ProxyHaveEnoughFeeValidation( - assetSourceRegistry, - accountRepository, - walletRepository, - extrinsicService - ) - } -} - -class ProxyHaveEnoughFeeValidation( - private val assetSourceRegistry: AssetSourceRegistry, - private val accountRepository: AccountRepository, - private val walletRepository: WalletRepository, - private val extrinsicService: ExtrinsicService, -) : Validation { - - override suspend fun validate(value: ProxiedExtrinsicValidationPayload): ValidationStatus { - val lastProxyMetaAccount = accountRepository.getLastProxyAccountFor(value.rootProxiedMetaAccount.id) - if (lastProxyMetaAccount == null) { - return validationError(ProxyMetaAccountNotFound) - } - val commissionAsset = walletRepository.getAsset(lastProxyMetaAccount.id, value.chain.commissionAsset)!! - val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(value.chain, commissionAsset.token.configuration) - val fee = getFee(value) - val availableBalanceToPayFee = commissionAsset.balanceCountedTowardsEDInPlanks - existentialDeposit - - return validOrError(availableBalanceToPayFee >= fee.amount) { - ProxyNotEnoughFee(lastProxyMetaAccount, availableBalanceToPayFee.atLeastZero(), fee) - } - } - - private suspend fun getFee(value: ProxiedExtrinsicValidationPayload): Fee { - return extrinsicService.estimateFeeV2(value.chain) { - call(value.call) - } - } -} - -fun ProxiedExtrinsicValidationSystemBuilder.proxyHaveEnoughFeeValidation( - proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory -) = validate(proxyHaveEnoughFeeValidationFactory.create()) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation2.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation2.kt deleted file mode 100644 index 2fe49a2d52..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/validation/ProxyHaveEnoughFeeValidation2.kt +++ /dev/null @@ -1,77 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation - -import io.novafoundation.nova.common.utils.atLeastZero -import io.novafoundation.nova.common.validation.Validation -import io.novafoundation.nova.common.validation.ValidationStatus -import io.novafoundation.nova.common.validation.ValidationSystemBuilder -import io.novafoundation.nova.common.validation.validOrError -import io.novafoundation.nova.common.validation.validationError -import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService -import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance -import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository -import io.novafoundation.nova.runtime.ext.commissionAsset -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall - -class ProxyHaveEnoughFeeValidation2( - private val assetSourceRegistry: AssetSourceRegistry, - private val accountRepository: AccountRepository, - private val walletRepository: WalletRepository, - private val extrinsicService: ExtrinsicService, - private val proxiedMetaAccount: (P) -> MetaAccount, - private val chain: (P) -> Chain, - private val callInstance: (P) -> GenericCall.Instance, - private val proxyMetaAccountNotFound: () -> E, - private val proxyNotEnoughFee: (proxy: MetaAccount, availableBalanceToPayFee: Balance, fee: Fee) -> E, -) : Validation { - - override suspend fun validate(value: P): ValidationStatus { - val lastProxyMetaAccount = accountRepository.getLastProxyAccountFor(proxiedMetaAccount(value).id) - if (lastProxyMetaAccount == null) { - return validationError(proxyMetaAccountNotFound()) - } - val commissionAsset = walletRepository.getAsset(lastProxyMetaAccount.id, chain(value).commissionAsset)!! - val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chain(value), commissionAsset.token.configuration) - val fee = getFee(value) - val availableBalanceToPayFee = commissionAsset.balanceCountedTowardsEDInPlanks - existentialDeposit - - return validOrError(availableBalanceToPayFee >= fee.amount) { - proxyNotEnoughFee(lastProxyMetaAccount, availableBalanceToPayFee.atLeastZero(), fee) - } - } - - private suspend fun getFee(value: P): Fee { - return extrinsicService.estimateFeeV2(chain(value)) { - call(callInstance(value)) - } - } -} - -fun ValidationSystemBuilder.proxyHaveEnoughFeeValidation2( - assetSourceRegistry: AssetSourceRegistry, - accountRepository: AccountRepository, - walletRepository: WalletRepository, - extrinsicService: ExtrinsicService, - proxiedMetaAccount: (P) -> MetaAccount, - chain: (P) -> Chain, - callInstance: (P) -> GenericCall.Instance, - proxyMetaAccountNotFound: () -> E, - proxyNotEnoughFee: (proxy: MetaAccount, availableBalanceToPayFee: Balance, fee: Fee) -> E, -) = validate( - ProxyHaveEnoughFeeValidation2( - assetSourceRegistry, - accountRepository, - walletRepository, - extrinsicService, - proxiedMetaAccount, - chain, - callInstance, - proxyMetaAccountNotFound, - proxyNotEnoughFee - ) -) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt index ba2d8ea218..624415e608 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureComponent.kt @@ -43,7 +43,6 @@ import io.novafoundation.nova.feature_account_impl.presentation.watchOnly.change import io.novafoundation.nova.feature_account_impl.presentation.watchOnly.create.di.CreateWatchWalletComponent import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi -import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi import io.novafoundation.nova.runtime.di.RuntimeApi import io.novafoundation.nova.web3names.di.Web3NamesApi @@ -129,7 +128,6 @@ interface AccountFeatureComponent : AccountFeatureApi { CommonApi::class, RuntimeApi::class, CurrencyFeatureApi::class, - WalletFeatureApi::class, DbApi::class, VersionsFeatureApi::class, Web3NamesApi::class diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt index e8a93a3d47..a9a575ff6d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt @@ -33,8 +33,6 @@ import io.novafoundation.nova.core_db.dao.NodeDao import io.novafoundation.nova.feature_currency_api.domain.CurrencyInteractor import io.novafoundation.nova.feature_currency_api.domain.interfaces.CurrencyRepository import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry -import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory @@ -119,10 +117,6 @@ interface AccountFeatureDependencies { fun computationalCache(): ComputationalCache - fun walletRepository(): WalletRepository - - fun assetSourceRegistry(): AssetSourceRegistry - val systemCallExecutor: SystemCallExecutor val multiChainQrSharingFactory: MultiChainQrSharingFactory diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureHolder.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureHolder.kt index b0aa375980..7037bd70ad 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureHolder.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureHolder.kt @@ -12,7 +12,6 @@ import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.Polk import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_currency_api.di.CurrencyFeatureApi import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi -import io.novafoundation.nova.feature_wallet_api.di.WalletFeatureApi import io.novafoundation.nova.runtime.di.RuntimeApi import io.novafoundation.nova.web3names.di.Web3NamesApi import javax.inject.Inject @@ -34,7 +33,6 @@ class AccountFeatureHolder @Inject constructor( .dbApi(getFeature(DbApi::class.java)) .runtimeApi(getFeature(RuntimeApi::class.java)) .currencyFeatureApi(getFeature(CurrencyFeatureApi::class.java)) - .walletFeatureApi(getFeature(WalletFeatureApi::class.java)) .versionsFeatureApi(getFeature(VersionsFeatureApi::class.java)) .web3NamesApi(getFeature(Web3NamesApi::class.java)) .build() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt index 88223c2cd6..c87794b86d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt @@ -4,60 +4,19 @@ import dagger.Module import dagger.Provides import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 import io.novafoundation.nova.common.di.scope.FeatureScope -import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService -import io.novafoundation.nova.common.utils.DefaultMutableSharedState -import io.novafoundation.nova.common.utils.MutableSharedState -import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository -import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider -import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter -import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator -import io.novafoundation.nova.feature_account_impl.data.signer.RealSignerProvider -import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner -import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.ParitySignerSigner -import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultSigner -import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxiedExtrinsicValidationSystem -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.ProxyHaveEnoughFeeValidationFactory -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.validation.proxiedExtrinsicValidationSystem -import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory -import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySigner -import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry -import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @Module class ProxiedSignerModule { @Provides @FeatureScope - fun provideProxyHaveEnoughFeeValidationFactory( - assetSourceRegistry: AssetSourceRegistry, - accountRepository: AccountRepository, - walletRepository: WalletRepository, - extrinsicService: ExtrinsicService - ) = ProxyHaveEnoughFeeValidationFactory( - assetSourceRegistry, - accountRepository, - walletRepository, - extrinsicService - ) - - @Provides - @FeatureScope - fun provideProxiedExtrinsicValidationSystem( - proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory - ): ProxiedExtrinsicValidationSystem { - return proxiedExtrinsicValidationSystem(proxyHaveEnoughFeeValidationFactory) - } + fun provideProxyExtrinsicValidationRequestBus() = ProxyExtrinsicValidationRequestBus() @Provides @FeatureScope @@ -67,7 +26,13 @@ class ProxiedSignerModule { accountRepository: AccountRepository, proxySigningPresenter: ProxySigningPresenter, proxyRepository: ProxyRepository, - proxiedExtrinsicValidationSystem: ProxiedExtrinsicValidationSystem, - ) = ProxiedSignerFactory(secretStoreV2, chainRegistry, accountRepository, proxySigningPresenter, proxyRepository, proxiedExtrinsicValidationSystem) - + proxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus + ) = ProxiedSignerFactory( + secretStoreV2, + chainRegistry, + accountRepository, + proxySigningPresenter, + proxyRepository, + proxyExtrinsicValidationRequestBus + ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt index 882e312457..7a25bc86a3 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt @@ -8,12 +8,10 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService import io.novafoundation.nova.common.utils.DefaultMutableSharedState import io.novafoundation.nova.common.utils.MutableSharedState -import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter -import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_impl.data.signer.RealSignerProvider import io.novafoundation.nova.feature_account_impl.data.signer.ledger.LedgerSigner diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt index 7fb03d3190..f7dd58d107 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt @@ -4,23 +4,16 @@ import android.text.SpannableStringBuilder import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.amountFromPlanks import io.novafoundation.nova.common.utils.colorSpan import io.novafoundation.nova.common.utils.formatting.format import io.novafoundation.nova.common.utils.formatting.spannable.SpannableFormatter import io.novafoundation.nova.common.utils.toSpannable import io.novafoundation.nova.common.view.dialog.dialog -import io.novafoundation.nova.common.view.dialog.errorDialog -import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable -import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks -import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import java.math.BigInteger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.coroutines.resume @@ -77,31 +70,13 @@ class RealProxySigningPresenter( ) } - override suspend fun notEnoughFee(proxyMetaAccount: MetaAccount, asset: Chain.Asset, availableBalanceToPayFee: BigInteger, fee: Fee) { + override suspend fun notEnoughFee(errorMessage: String) = withContext(Dispatchers.Main) { suspendCoroutine { continuation -> - dialog(contextManager.getApplicationContext()) { + dialog(contextManager.getActivity()!!) { setTitle(R.string.error_not_enough_to_pay_fee_title) - setMessage( - resourceManager.getString( - R.string.proxy_error_not_enough_to_pay_fee_message, - proxyMetaAccount.name, - asset.amountFromPlanks(fee.amount).formatTokenAmount(asset), - asset.amountFromPlanks(availableBalanceToPayFee).formatTokenAmount(asset), - ) - ) - - setPositiveButton(io.novafoundation.nova.common.R.string.common_close, null) - setOnDismissListener { continuation.resume(Unit) } - } - } - } + setMessage(errorMessage) - override suspend fun unsupportedValidationError() { - suspendCoroutine { continuation -> - dialog(contextManager.getApplicationContext()) { - setTitle(R.string.common_unknown_error) - setPositiveButton(io.novafoundation.nova.common.R.string.common_close, null) - setOnDismissListener { continuation.resume(Unit) } + setPositiveButton(io.novafoundation.nova.common.R.string.common_close) { _, _ -> continuation.resume(Unit) } } } } diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/di/WalletFeatureApi.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/di/WalletFeatureApi.kt index 11a85c2ae2..8a22d8f4af 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/di/WalletFeatureApi.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/di/WalletFeatureApi.kt @@ -20,6 +20,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstan import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.PhishingValidationFactory +import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.runtime.ethereum.contract.erc20.Erc20Standard @@ -44,6 +45,8 @@ interface WalletFeatureApi { fun enoughTotalToStayAboveEDValidationFactory(): EnoughTotalToStayAboveEDValidationFactory + fun proxyHaveEnoughFeeValidationFactory(): ProxyHaveEnoughFeeValidationFactory + val phishingValidationFactory: PhishingValidationFactory val crossChainTransfersRepository: CrossChainTransfersRepository diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt new file mode 100644 index 0000000000..3120b838e9 --- /dev/null +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt @@ -0,0 +1,85 @@ +package io.novafoundation.nova.feature_wallet_api.domain.validation + +import io.novafoundation.nova.common.utils.atLeastZero +import io.novafoundation.nova.common.validation.Validation +import io.novafoundation.nova.common.validation.ValidationStatus +import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.common.validation.validOrError +import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +class ProxyHaveEnoughFeeValidationFactory( + private val assetSourceRegistry: AssetSourceRegistry, + private val walletRepository: WalletRepository, + private val extrinsicService: ExtrinsicService +) { + fun create( + proxyAccountId: (P) -> AccountId, + call: (P) -> GenericCall.Instance, + chainWithAsset: (P) -> ChainWithAsset, + proxyNotEnoughFee: (payload: P, availableBalance: Balance, fee: Fee) -> E, + ): ProxyHaveEnoughFeeValidation { + return ProxyHaveEnoughFeeValidation( + assetSourceRegistry, + walletRepository, + extrinsicService, + proxyAccountId, + call, + chainWithAsset, + proxyNotEnoughFee + ) + } +} + +class ProxyHaveEnoughFeeValidation( + private val assetSourceRegistry: AssetSourceRegistry, + private val walletRepository: WalletRepository, + private val extrinsicService: ExtrinsicService, + private val proxyAccountId: (P) -> AccountId, + private val call: (P) -> GenericCall.Instance, + private val chainWithAsset: (P) -> ChainWithAsset, + private val proxyNotEnoughFee: (payload: P, availableBalance: Balance, fee: Fee) -> E, +) : Validation { + + override suspend fun validate(value: P): ValidationStatus { + val chain = chainWithAsset(value).chain + val chainAsset = chainWithAsset(value).asset + val fee = calculateFee(chain, call(value)) + val asset = walletRepository.getAsset(proxyAccountId(value), chainAsset)!! + val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chain, asset.token.configuration) + val availableBalanceToPayFee = (asset.balanceCountedTowardsEDInPlanks - existentialDeposit).atLeastZero() + + return validOrError(availableBalanceToPayFee >= fee.amount) { + proxyNotEnoughFee(value, availableBalanceToPayFee, fee) + } + } + + private suspend fun calculateFee(chain: Chain, callInstance: GenericCall.Instance): Fee { + return extrinsicService.estimateFeeV2(chain) { + call(callInstance) + } + } +} + +fun ValidationSystemBuilder.proxyHasEnoughFeeValidation( + factory: ProxyHaveEnoughFeeValidationFactory, + proxyAccountId: (P) -> AccountId, + call: (P) -> GenericCall.Instance, + chainWithAsset: (P) -> ChainWithAsset, + proxyNotEnoughFee: (payload: P, availableBalance: Balance, fee: Fee) -> E, +) = validate( + factory.create( + proxyAccountId, + call, + chainWithAsset, + proxyNotEnoughFee + ) +) diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureModule.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureModule.kt index a750f348ee..c8221cf957 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureModule.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/di/WalletFeatureModule.kt @@ -47,6 +47,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstan import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.PhishingValidationFactory +import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserProviderFactory import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin @@ -332,4 +333,16 @@ class WalletFeatureModule { ): SubstrateRealtimeOperationFetcher.Factory { return SubstrateRealtimeOperationFetcherFactory(multiLocationConverterFactory, eventsRepository) } + + @Provides + @FeatureScope + fun provideProxyHaveEnoughFeeValidationFactory( + assetSourceRegistry: AssetSourceRegistry, + walletRepository: WalletRepository, + extrinsicService: ExtrinsicService, + ) = ProxyHaveEnoughFeeValidationFactory( + assetSourceRegistry, + walletRepository, + extrinsicService + ) } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt index 9e1c2ab84e..5be9f7d720 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/storage/source/query/BaseStorageQueryContext.kt @@ -7,7 +7,6 @@ import io.novafoundation.nova.common.data.network.runtime.binding.fromHexOrIncom import io.novafoundation.nova.common.data.network.runtime.binding.incompatible import io.novafoundation.nova.common.utils.ComponentHolder import io.novafoundation.nova.common.utils.mapValuesNotNull -import io.novafoundation.nova.common.utils.splitKeyToComponents import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilderImpl From 3653daf4b1baa100130be07e967191d2c8cab10f Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 21 Dec 2023 03:38:05 +0100 Subject: [PATCH 062/100] Update MetaAccountDao.kt --- .../io/novafoundation/nova/core_db/dao/MetaAccountDao.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 8ca5196bde..fb51b56d49 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -191,17 +191,17 @@ interface MetaAccountDao { @Query( """ - WITH RECURSIVE Chain AS ( + WITH RECURSIVE Sequence AS ( SELECT pa.proxyMetaId FROM proxy_accounts pa WHERE pa.proxiedMetaId = :proxiedMetaId UNION ALL SELECT pa.proxyMetaId FROM proxy_accounts pa - INNER JOIN Chain c ON c.proxyMetaId = pa.proxiedMetaId + INNER JOIN Sequence s ON s.proxyMetaId = pa.proxiedMetaId ) SELECT MAX(proxyMetaId) as finalMetaId - FROM Chain + FROM Sequence """ ) fun getLastProxyAccountId(proxiedMetaId: Long): Long? From a85451fc7cb12202becf1b01c10359d39d2b14c8 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 21 Dec 2023 03:45:35 +0100 Subject: [PATCH 063/100] Run ktlint --- .../data/signer/proxy/ProxiedSigner.kt | 1 - .../di/modules/signers/ProxiedSignerModule.kt | 8 ++++---- .../di/modules/signers/SignersModule.kt | 3 --- 3 files changed, 4 insertions(+), 8 deletions(-) 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 94c4b6d21e..70e7e60240 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 @@ -21,7 +21,6 @@ import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.network.rpc.RpcCalls import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt index c87794b86d..33e103dbf8 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt @@ -2,7 +2,6 @@ package io.novafoundation.nova.feature_account_impl.di.modules.signers import dagger.Module import dagger.Provides -import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository @@ -10,6 +9,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.Pr import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.network.rpc.RpcCalls @Module class ProxiedSignerModule { @@ -21,18 +21,18 @@ class ProxiedSignerModule { @Provides @FeatureScope fun provideProxiedSignerFactory( - secretStoreV2: SecretStoreV2, chainRegistry: ChainRegistry, accountRepository: AccountRepository, proxySigningPresenter: ProxySigningPresenter, proxyRepository: ProxyRepository, - proxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus + rpcCalls: RpcCalls, + proxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus, ) = ProxiedSignerFactory( - secretStoreV2, chainRegistry, accountRepository, proxySigningPresenter, proxyRepository, + rpcCalls, proxyExtrinsicValidationRequestBus ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt index 4005fecf39..901d364e5b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt @@ -11,8 +11,6 @@ import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider -import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter -import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_impl.data.signer.RealSignerProvider @@ -25,7 +23,6 @@ import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSi import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySignerFactory import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import io.novafoundation.nova.runtime.network.rpc.RpcCalls import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @Module(includes = [ProxiedSignerModule::class]) From f6139ca81ba0b8ffc605db77327cb9c6662093d3 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 21 Dec 2023 03:57:15 +0100 Subject: [PATCH 064/100] Remove dependency in account build module --- feature-account-impl/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/feature-account-impl/build.gradle b/feature-account-impl/build.gradle index 41ea4fa182..7d09470dce 100644 --- a/feature-account-impl/build.gradle +++ b/feature-account-impl/build.gradle @@ -42,7 +42,6 @@ dependencies { implementation project(':feature-currency-api') implementation project(':feature-ledger-api') implementation project(':feature-versions-api') - implementation project(':feature-wallet-api') implementation project(':web3names') implementation kotlinDep From 629741e556678464639194e66ef53eb9bc8e7943 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 21 Dec 2023 12:20:50 +0100 Subject: [PATCH 065/100] Disable removing proxieds --- .../account/listing/holders/AccountHolder.kt | 3 ++- .../account/listing/items/AccountUi.kt | 1 + .../DelegatedMetaAccountUpdatesListingMixin.kt | 5 +++-- .../MetaAccountValidForTransactionListingMixin.kt | 3 ++- .../listing/MetaAccountWithBalanceListingMixin.kt | 13 +++++++++++++ .../selectAddress/SelectAddressLedgerViewModel.kt | 3 ++- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt index 7dc36190d8..46f1f3dde1 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/holders/AccountHolder.kt @@ -108,7 +108,8 @@ class AccountHolder(view: View, private val imageLoader: ImageLoader, @ColorRes Mode.EDIT -> { itemAccountArrow.visibility = View.INVISIBLE - itemAccountDelete.visibility = View.VISIBLE + itemAccountDelete.isVisible = accountModel.isEditable + itemAccountDelete.setOnClickListener { handler?.deleteClicked(accountModel) } itemAccountDelete.setImageResource(R.drawable.ic_delete_symbol) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt index 4a260ee54d..7f17c15877 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/listing/items/AccountUi.kt @@ -7,6 +7,7 @@ class AccountUi( val title: CharSequence, val subtitle: CharSequence, val isSelected: Boolean, + val isEditable: Boolean, val isClickable: Boolean, val picture: Drawable, val chainIconUrl: String?, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt index ef140a8589..0467c7d53a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt @@ -77,9 +77,10 @@ private class DelegatedMetaAccountUpdatesListingMixin( isClickable = true, picture = if (isEnabled) walletIcon else walletIcon.withAlphaDrawable(ICON_ALPHA), chainIconUrl = proxiedWithProxy.chain.icon, - chainIconOpacity = ICON_ALPHA, + updateIndicator = false, subtitleIconRes = null, - updateIndicator = false + chainIconOpacity = ICON_ALPHA, + isEditable = false ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt index c74521b077..9c6645ba91 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountValidForTransactionListingMixin.kt @@ -81,7 +81,8 @@ private class MetaAccountValidForTransactionListingMixin( picture = icon, chainIconUrl = null, updateIndicator = false, - subtitleIconRes = if (chainAddress == null) R.drawable.ic_warning_filled else null + subtitleIconRes = if (chainAddress == null) R.drawable.ic_warning_filled else null, + isEditable = false ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt index 326a863dd9..f0da9558ac 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/MetaAccountWithBalanceListingMixin.kt @@ -61,6 +61,7 @@ private class MetaAccountWithBalanceListingMixin( title = metaAccount.name, subtitle = mapSubtitle(this), isSelected = isMetaAccountSelected(metaAccount), + isEditable = metaAccount.isEditable(), isClickable = true, picture = walletUiUseCase.walletIcon(metaAccount), chainIconUrl = proxyChain?.icon, @@ -96,6 +97,18 @@ private class MetaAccountWithBalanceListingMixin( ) } + private fun MetaAccount.isEditable(): Boolean { + return when (type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.WATCH_ONLY, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.LEDGER, + LightMetaAccount.Type.POLKADOT_VAULT -> true + + LightMetaAccount.Type.PROXIED -> false + } + } + private fun MetaAccountWithTotalBalance.formattedTotalBalance(): String { return totalBalance.formatAsCurrency(currency) } diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt index 3e896c259a..687fa01765 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/common/selectAddress/SelectAddressLedgerViewModel.kt @@ -159,7 +159,8 @@ abstract class SelectAddressLedgerViewModel( picture = addressModel.image, chainIconUrl = null, updateIndicator = false, - subtitleIconRes = null + subtitleIconRes = null, + isEditable = false ) } } From a36efbc1d37e36277846a195ea64d7499916e1c9 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Mon, 25 Dec 2023 19:28:10 +0100 Subject: [PATCH 066/100] Fixed pr notes --- ...oxyExtrinsicValidationRequestBusHandler.kt | 27 +++++++----------- .../ProxiedExtrinsicValidationSystem.kt | 10 ++++++- .../data/signer/SignerProvider.kt | 4 ++- .../account/proxy/ProxySigningPresenter.kt | 5 +++- feature-account-impl/build.gradle | 1 + .../transaction/RealEvmTransactionService.kt | 2 +- .../data/extrinsic/RealExtrinsicService.kt | 4 +-- .../data/proxy/RealProxySyncService.kt | 7 ++++- .../data/signer/RealSignerProvider.kt | 26 +++++++++++------ .../data/signer/proxy/ProxiedSigner.kt | 28 ++++++++----------- .../proxy/sign/RealProxySigningPresenter.kt | 24 ++++++++++++++-- .../custom/acala/AcalaContributeInteractor.kt | 2 +- .../moonbeam/MoonbeamCrowdloanInteractor.kt | 2 +- .../domain/sign/BaseExternalSignInteractor.kt | 2 +- .../ProxyHaveEnoughFeeValidation.kt | 18 ++++++++---- 15 files changed, 102 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt index 1dbf21d2e4..c6314a6169 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt @@ -5,25 +5,23 @@ import io.novafoundation.nova.common.utils.bus.observeBusEvent import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.common.validation.ValidationSystemBuilder -import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus.ValidationResponse import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationFailure import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationPayload -import io.novafoundation.nova.feature_account_impl.R -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance -import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks +import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.proxyHasEnoughFeeValidation -import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount import kotlinx.coroutines.flow.launchIn class ProxyExtrinsicValidationRequestBusHandler( private val scope: RootScope, private val proxyProxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus, private val proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory, + private val walletRepository: WalletRepository, private val resourceManager: ResourceManager ) : RequestBusHandler { + override fun observe() { proxyProxyExtrinsicValidationRequestBus.observeEvent() .observeBusEvent { request -> @@ -42,23 +40,18 @@ class ProxyExtrinsicValidationRequestBusHandler( private fun ValidationSystemBuilder.proxyHasEnoughFee() { proxyHasEnoughFeeValidation( factory = proxyHaveEnoughFeeValidationFactory, + metaAccount = { it.proxyMetaAccount }, proxyAccountId = { it.proxyAccountId }, call = { it.call }, chainWithAsset = { it.chainWithAsset }, proxyNotEnoughFee = { payload, availableBalance, fee -> - val errorMessage = buildNotEnoughFeeMessage(payload, availableBalance, fee) - ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee(errorMessage) + ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee( + payload.proxyMetaAccount, + payload.chainWithAsset.asset, + availableBalance, + fee + ) } ) } - - private fun buildNotEnoughFeeMessage(payload: ProxiedExtrinsicValidationPayload, availableBalance: Balance, fee: Fee): String { - val asset = payload.chainWithAsset.asset - return resourceManager.getString( - R.string.proxy_error_not_enough_to_pay_fee_message, - payload.proxyMetaAccount.name, - asset.amountFromPlanks(fee.amount).formatTokenAmount(asset), - asset.amountFromPlanks(availableBalance).formatTokenAmount(asset), - ) - } } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxiedExtrinsicValidationSystem.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxiedExtrinsicValidationSystem.kt index 40e409fe94..21d8b205d5 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxiedExtrinsicValidationSystem.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/validation/ProxiedExtrinsicValidationSystem.kt @@ -2,8 +2,11 @@ package io.novafoundation.nova.feature_account_api.data.proxy.validation import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import java.math.BigInteger import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall @@ -19,5 +22,10 @@ class ProxiedExtrinsicValidationPayload( sealed interface ProxiedExtrinsicValidationFailure { - class ProxyNotEnoughFee(val errorMessage: String) : ProxiedExtrinsicValidationFailure + class ProxyNotEnoughFee( + val metaAccount: MetaAccount, + val asset: Chain.Asset, + val availableBalance: BigInteger, + val fee: Fee + ) : ProxiedExtrinsicValidationFailure } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt index 35291e260c..df8baec5e1 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt @@ -6,7 +6,9 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain interface SignerProvider { - fun signerFor(metaAccount: MetaAccount): NovaSigner + fun rootSignerFor(metaAccount: MetaAccount): NovaSigner + + fun nestedSignerFor(metaAccount: MetaAccount): NovaSigner fun feeSigner(metaAccount: MetaAccount, chain: Chain): NovaSigner } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt index f2bf6dc47f..98f8682d84 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/proxy/ProxySigningPresenter.kt @@ -1,7 +1,10 @@ package io.novafoundation.nova.feature_account_api.presenatation.account.proxy +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import java.math.BigInteger interface ProxySigningPresenter { @@ -11,5 +14,5 @@ interface ProxySigningPresenter { suspend fun signingIsNotSupported() - suspend fun notEnoughFee(errorMessage: String) + suspend fun notEnoughFee(metaAccount: MetaAccount, chainAsset: Chain.Asset, availableBalance: BigInteger, fee: Fee) } diff --git a/feature-account-impl/build.gradle b/feature-account-impl/build.gradle index 7d09470dce..5909d7a896 100644 --- a/feature-account-impl/build.gradle +++ b/feature-account-impl/build.gradle @@ -39,6 +39,7 @@ dependencies { implementation project(':common') implementation project(':runtime') implementation project(':feature-account-api') + implementation project(':feature-wallet-api') implementation project(':feature-currency-api') implementation project(':feature-ledger-api') implementation project(':feature-versions-api') diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index 2fe622a8fc..2b15177554 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -100,7 +100,7 @@ internal class RealEvmTransactionService( val ethereumChainId = chain.addressPrefix.toLong() val encodedTx = TransactionEncoder.encode(txForSign, ethereumChainId) - val signer = signerProvider.signerFor(metaAccount) + val signer = signerProvider.rootSignerFor(metaAccount) val accountId = metaAccount.requireAccountIdIn(chain) val signerPayload = SignerPayloadRaw(encodedTx, accountId) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index a4c1d1eaff..3b95f30626 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -144,7 +144,7 @@ class RealExtrinsicService( formExtrinsic: FormMultiExtrinsicWithOrigin, ): List { val metaAccount = accountRepository.requireMetaAccountFor(origin, chain.id) - val signer = signerProvider.signerFor(metaAccount) + val signer = signerProvider.rootSignerFor(metaAccount) val requestedOrigin = metaAccount.requireAccountIdIn(chain) val actualOrigin = signer.signerAccountId(chain) @@ -186,7 +186,7 @@ class RealExtrinsicService( metaAccount: MetaAccount, formExtrinsic: FormExtrinsicWithOrigin, ): SubmissionRaw { - val signer = signerProvider.signerFor(metaAccount) + val signer = signerProvider.rootSignerFor(metaAccount) val requestedOrigin = metaAccount.requireAccountIdIn(chain) val actualOrigin = signer.signerAccountId(chain) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 24e37988a0..53db0b0289 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -26,6 +26,9 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.findChains import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.withTimeoutOrNull + +private const val SYNC_TIMEOUT = 10_000L // 10 seconds class RealProxySyncService( private val chainRegistry: ChainRegistry, @@ -45,7 +48,9 @@ class RealProxySyncService( val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> - proxyRepository.getAllProxiesForMetaAccounts(chain.id, accountIds) + withTimeoutOrNull(SYNC_TIMEOUT) { + proxyRepository.getAllProxiesForMetaAccounts(chain.id, accountIds) + } ?: emptyList() } val oldProxies = accountDao.getAllProxyAccounts() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt index 3de19b9e33..65f0a62971 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt @@ -22,15 +22,12 @@ internal class RealSignerProvider( private val ledgerSignerFactory: LedgerSignerFactory, ) : SignerProvider { - override fun signerFor(metaAccount: MetaAccount): NovaSigner { - return when (metaAccount.type) { - LightMetaAccount.Type.SECRETS -> secretsSignerFactory.create(metaAccount) - LightMetaAccount.Type.WATCH_ONLY -> watchOnlySigner.create(metaAccount) - LightMetaAccount.Type.PARITY_SIGNER -> polkadotVaultSignerFactory.createParitySigner(metaAccount) - LightMetaAccount.Type.POLKADOT_VAULT -> polkadotVaultSignerFactory.createPolkadotVault(metaAccount) - LightMetaAccount.Type.LEDGER -> ledgerSignerFactory.create(metaAccount) - LightMetaAccount.Type.PROXIED -> proxiedSignerFactory.create(metaAccount, this) - } + override fun rootSignerFor(metaAccount: MetaAccount): NovaSigner { + return signerFor(metaAccount, isRoot = true) + } + + override fun nestedSignerFor(metaAccount: MetaAccount): NovaSigner { + return signerFor(metaAccount, isRoot = false) } override fun feeSigner(metaAccount: MetaAccount, chain: Chain): NovaSigner { @@ -44,4 +41,15 @@ internal class RealSignerProvider( LightMetaAccount.Type.PROXIED -> proxiedFeeSignerFactory.create(metaAccount, chain, this) } } + + private fun signerFor(metaAccount: MetaAccount, isRoot: Boolean): NovaSigner { + return when (metaAccount.type) { + LightMetaAccount.Type.SECRETS -> secretsSignerFactory.create(metaAccount) + LightMetaAccount.Type.WATCH_ONLY -> watchOnlySigner.create(metaAccount) + LightMetaAccount.Type.PARITY_SIGNER -> polkadotVaultSignerFactory.createParitySigner(metaAccount) + LightMetaAccount.Type.POLKADOT_VAULT -> polkadotVaultSignerFactory.createPolkadotVault(metaAccount) + LightMetaAccount.Type.LEDGER -> ledgerSignerFactory.create(metaAccount) + LightMetaAccount.Type.PROXIED -> proxiedSignerFactory.create(metaAccount, this, isRoot) + } + } } 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 70e7e60240..79404825c6 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 @@ -10,7 +10,6 @@ import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.ProxyType -import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAddressIn import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter @@ -37,7 +36,7 @@ class ProxiedSignerFactory( private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus ) { - fun create(metaAccount: MetaAccount, signerProvider: SignerProvider): ProxiedSigner { + fun create(metaAccount: MetaAccount, signerProvider: SignerProvider, isRoot: Boolean): ProxiedSigner { return ProxiedSigner( proxiedMetaAccount = metaAccount, chainRegistry = chainRegistry, @@ -46,7 +45,8 @@ class ProxiedSignerFactory( proxySigningPresenter = proxySigningPresenter, proxyRepository = proxyRepository, rpcCalls = rpcCalls, - proxyExtrinsicValidationEventBus = proxyExtrinsicValidationEventBus + proxyExtrinsicValidationEventBus = proxyExtrinsicValidationEventBus, + isRootProxied = isRoot ) } } @@ -59,7 +59,8 @@ class ProxiedSigner( private val proxySigningPresenter: ProxySigningPresenter, private val proxyRepository: ProxyRepository, private val rpcCalls: RpcCalls, - private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus + private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus, + private val isRootProxied: Boolean ) : NovaSigner { override suspend fun signerAccountId(chain: Chain): AccountId { @@ -75,7 +76,7 @@ class ProxiedSigner( acknowledgeProxyOperation(proxyMetaAccount) - if (isRootProxiedSigner()) { + if (isRootProxied) { validateExtrinsic(payloadExtrinsic, chain) } @@ -91,14 +92,14 @@ class ProxiedSigner( } private fun createDelegate(proxyMetaAccount: MetaAccount): NovaSigner { - return signerProvider.signerFor(proxyMetaAccount) + return signerProvider.nestedSignerFor(proxyMetaAccount) } - private suspend fun validateExtrinsic(signedExtrinsic: SignerPayloadExtrinsic, chain: Chain) { - val proxyAccount = accountRepository.getLastProxyAccountFor(proxiedMetaAccount.id) ?: throw IllegalStateException("Proxy account is not found") - val proxyAccountId = proxyAccount.accountIdIn(chain) ?: throw IllegalStateException("Proxy account is not found") + private suspend fun validateExtrinsic(extrinsicPayload: SignerPayloadExtrinsic, chain: Chain) { + val proxyAccountId = signerAccountId(chain) + val proxyAccount = accountRepository.findMetaAccount(proxyAccountId, chain.id) ?: throw IllegalStateException("Proxy account is not found") - val callInstance = signedExtrinsic.call.toCallInstance() ?: signingNotSupported() + val callInstance = extrinsicPayload.call.toCallInstance() ?: signingNotSupported() val validationPayload = ProxiedExtrinsicValidationPayload( proxyAccount, @@ -113,7 +114,7 @@ class ProxiedSigner( val validationStatus = validationResponse.validationResult.getOrNull() if (validationStatus is ValidationStatus.NotValid && validationStatus.reason is ProxyNotEnoughFee) { val reason = validationStatus.reason as ProxyNotEnoughFee - proxySigningPresenter.notEnoughFee(reason.errorMessage) + proxySigningPresenter.notEnoughFee(reason.metaAccount, reason.asset, reason.availableBalance, reason.fee) throw SigningCancelledException() } @@ -147,11 +148,6 @@ class ProxiedSigner( ) } - private suspend fun isRootProxiedSigner(): Boolean { - val selectedMetaAccount = accountRepository.getSelectedMetaAccount() - return selectedMetaAccount.id == proxiedMetaAccount.id - } - private suspend fun acknowledgeProxyOperation(proxyMetaAccount: MetaAccount) { val resume = proxySigningPresenter.acknowledgeProxyOperation(proxiedMetaAccount, proxyMetaAccount) if (!resume) { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt index f7dd58d107..ccd55fdb37 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt @@ -5,15 +5,19 @@ import io.novafoundation.nova.common.data.storage.Preferences import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.colorSpan -import io.novafoundation.nova.common.utils.formatting.format import io.novafoundation.nova.common.utils.formatting.spannable.SpannableFormatter import io.novafoundation.nova.common.utils.toSpannable import io.novafoundation.nova.common.view.dialog.dialog +import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable +import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks +import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import java.math.BigInteger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.coroutines.resume @@ -70,11 +74,25 @@ class RealProxySigningPresenter( ) } - override suspend fun notEnoughFee(errorMessage: String) = withContext(Dispatchers.Main) { + override suspend fun notEnoughFee( + metaAccount: MetaAccount, + chainAsset: Chain.Asset, + availableBalance: BigInteger, + fee: Fee + ) = withContext(Dispatchers.Main) { suspendCoroutine { continuation -> dialog(contextManager.getActivity()!!) { setTitle(R.string.error_not_enough_to_pay_fee_title) - setMessage(errorMessage) + setMessage( + resourceManager.getString( + R.string.proxy_error_not_enough_to_pay_fee_message, + metaAccount.name, + chainAsset.amountFromPlanks(fee.amount).formatTokenAmount(chainAsset), + chainAsset.amountFromPlanks(availableBalance).formatTokenAmount(chainAsset), + ) + ) + + chainAsset.amountFromPlanks(availableBalance).formatTokenAmount(chainAsset) setPositiveButton(io.novafoundation.nova.common.R.string.common_close) { _, _ -> continuation.resume(Unit) } } diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/acala/AcalaContributeInteractor.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/acala/AcalaContributeInteractor.kt index dd0b69430c..9be399dd0c 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/acala/AcalaContributeInteractor.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/acala/AcalaContributeInteractor.kt @@ -42,7 +42,7 @@ class AcalaContributeInteractor( ): Result = runCatching { httpExceptionHandler.wrap { val selectedMetaAccount = accountRepository.getSelectedMetaAccount() - val signer = signerProvider.signerFor(selectedMetaAccount) + val signer = signerProvider.rootSignerFor(selectedMetaAccount) val (chain, chainAsset) = selectedAssetState.chainAndAsset() diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/moonbeam/MoonbeamCrowdloanInteractor.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/moonbeam/MoonbeamCrowdloanInteractor.kt index 79adf7306c..5d98cb8000 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/moonbeam/MoonbeamCrowdloanInteractor.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/custom/moonbeam/MoonbeamCrowdloanInteractor.kt @@ -125,7 +125,7 @@ class MoonbeamCrowdloanInteractor( val legalText = httpExceptionHandler.wrap { moonbeamApi.getLegalText() } val legalHash = legalText.encodeToByteArray().sha256().toHexString(withPrefix = false) - val signer = signerProvider.signerFor(metaAccount) + val signer = signerProvider.rootSignerFor(metaAccount) val signerPayload = SignerPayloadRaw.fromUtf8(legalHash, accountId) val signedHash = signer.signRaw(signerPayload).asHexString() diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/BaseExternalSignInteractor.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/BaseExternalSignInteractor.kt index 89d6e08022..2dd09eb242 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/BaseExternalSignInteractor.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/BaseExternalSignInteractor.kt @@ -15,7 +15,7 @@ abstract class BaseExternalSignInteractor( protected suspend fun resolveWalletSigner(): NovaSigner { val metaAccount = resolveMetaAccount() - return signerProvider.signerFor(metaAccount) + return signerProvider.rootSignerFor(metaAccount) } protected suspend fun resolveMetaAccount(): MetaAccount { diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt index 3120b838e9..344a0dac6b 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt @@ -5,8 +5,10 @@ import io.novafoundation.nova.common.validation.Validation import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.ValidationSystemBuilder import io.novafoundation.nova.common.validation.validOrError +import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.intoOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance @@ -23,6 +25,7 @@ class ProxyHaveEnoughFeeValidationFactory( ) { fun create( proxyAccountId: (P) -> AccountId, + metaAccount: (P) -> MetaAccount, call: (P) -> GenericCall.Instance, chainWithAsset: (P) -> ChainWithAsset, proxyNotEnoughFee: (payload: P, availableBalance: Balance, fee: Fee) -> E, @@ -31,6 +34,7 @@ class ProxyHaveEnoughFeeValidationFactory( assetSourceRegistry, walletRepository, extrinsicService, + metaAccount, proxyAccountId, call, chainWithAsset, @@ -43,6 +47,7 @@ class ProxyHaveEnoughFeeValidation( private val assetSourceRegistry: AssetSourceRegistry, private val walletRepository: WalletRepository, private val extrinsicService: ExtrinsicService, + private val metaAccount: (P) -> MetaAccount, private val proxyAccountId: (P) -> AccountId, private val call: (P) -> GenericCall.Instance, private val chainWithAsset: (P) -> ChainWithAsset, @@ -52,18 +57,19 @@ class ProxyHaveEnoughFeeValidation( override suspend fun validate(value: P): ValidationStatus { val chain = chainWithAsset(value).chain val chainAsset = chainWithAsset(value).asset - val fee = calculateFee(chain, call(value)) + val fee = calculateFee(metaAccount(value), chain, call(value)) val asset = walletRepository.getAsset(proxyAccountId(value), chainAsset)!! val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chain, asset.token.configuration) + val transferable = asset.transferableInPlanks val availableBalanceToPayFee = (asset.balanceCountedTowardsEDInPlanks - existentialDeposit).atLeastZero() - return validOrError(availableBalanceToPayFee >= fee.amount) { - proxyNotEnoughFee(value, availableBalanceToPayFee, fee) + return validOrError(transferable >= fee.amount && availableBalanceToPayFee >= fee.amount) { + proxyNotEnoughFee(value, transferable, fee) } } - private suspend fun calculateFee(chain: Chain, callInstance: GenericCall.Instance): Fee { - return extrinsicService.estimateFeeV2(chain) { + private suspend fun calculateFee(metaAccount: MetaAccount, chain: Chain, callInstance: GenericCall.Instance): Fee { + return extrinsicService.estimateFee(chain, metaAccount.intoOrigin()) { call(callInstance) } } @@ -71,6 +77,7 @@ class ProxyHaveEnoughFeeValidation( fun ValidationSystemBuilder.proxyHasEnoughFeeValidation( factory: ProxyHaveEnoughFeeValidationFactory, + metaAccount: (P) -> MetaAccount, proxyAccountId: (P) -> AccountId, call: (P) -> GenericCall.Instance, chainWithAsset: (P) -> ChainWithAsset, @@ -78,6 +85,7 @@ fun ValidationSystemBuilder.proxyHasEnoughFeeValidation( ) = validate( factory.create( proxyAccountId, + metaAccount, call, chainWithAsset, proxyNotEnoughFee From b65b349e9634f5bb415cefe7aa0f60305295d446 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Mon, 25 Dec 2023 19:33:07 +0100 Subject: [PATCH 067/100] Update ProxyHaveEnoughFeeValidation.kt --- .../domain/validation/ProxyHaveEnoughFeeValidation.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt index 344a0dac6b..424d3e4809 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt @@ -61,9 +61,9 @@ class ProxyHaveEnoughFeeValidation( val asset = walletRepository.getAsset(proxyAccountId(value), chainAsset)!! val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chain, asset.token.configuration) val transferable = asset.transferableInPlanks - val availableBalanceToPayFee = (asset.balanceCountedTowardsEDInPlanks - existentialDeposit).atLeastZero() + val balanceWithoutEd = (asset.balanceCountedTowardsEDInPlanks - existentialDeposit).atLeastZero() - return validOrError(transferable >= fee.amount && availableBalanceToPayFee >= fee.amount) { + return validOrError(transferable >= fee.amount && balanceWithoutEd >= fee.amount) { proxyNotEnoughFee(value, transferable, fee) } } From b81cb9020e365964f0a054279774acf8937e630c Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Mon, 25 Dec 2023 19:41:12 +0100 Subject: [PATCH 068/100] Update ProxyExtrinsicValidationRequestBusHandler.kt --- .../ProxyExtrinsicValidationRequestBusHandler.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt index c6314a6169..7fc6947b64 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt @@ -18,7 +18,6 @@ class ProxyExtrinsicValidationRequestBusHandler( private val scope: RootScope, private val proxyProxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus, private val proxyHaveEnoughFeeValidationFactory: ProxyHaveEnoughFeeValidationFactory, - private val walletRepository: WalletRepository, private val resourceManager: ResourceManager ) : RequestBusHandler { From c2bc0b398a12f48b723fca6ececd6f3094c1a62e Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Mon, 25 Dec 2023 19:41:26 +0100 Subject: [PATCH 069/100] Update ProxyExtrinsicValidationRequestBusHandler.kt --- .../ProxyExtrinsicValidationRequestBusHandler.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt index 7fc6947b64..0485b4ecd0 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/requestBusHandler/ProxyExtrinsicValidationRequestBusHandler.kt @@ -9,7 +9,6 @@ import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExt import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus.ValidationResponse import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationFailure import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationPayload -import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.validation.ProxyHaveEnoughFeeValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.proxyHasEnoughFeeValidation import kotlinx.coroutines.flow.launchIn From 721fa0d1a5d8fd00cd89cae295fe0eb5cec99db9 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Tue, 26 Dec 2023 11:04:13 +0300 Subject: [PATCH 070/100] Add dwellir secret (#1295) --- .github/workflows/android_build.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/android_build.yml b/.github/workflows/android_build.yml index bb807afa61..9f99797ba2 100644 --- a/.github/workflows/android_build.yml +++ b/.github/workflows/android_build.yml @@ -44,6 +44,8 @@ on: required: true INFURA_API_KEY: required: true + DWELLIR_API_KEY: + required: true WALLET_CONNECT_PROJECT_ID: required: true # Special secrets for signing: @@ -81,6 +83,7 @@ env: EHTERSCAN_API_KEY_MOONRIVER: ${{ secrets.EHTERSCAN_API_KEY_MOONRIVER }} EHTERSCAN_API_KEY_ETHEREUM: ${{ secrets.EHTERSCAN_API_KEY_ETHEREUM }} INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + DWELLIR_API_KEY: ${{ secrets.DWELLIR_API_KEY }} WALLET_CONNECT_PROJECT_ID: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} CI_MARKET_KEYSTORE_PASS: ${{ secrets.CI_MARKET_KEYSTORE_PASS }} @@ -123,7 +126,7 @@ jobs: fileName: ${{ inputs.keystore-file-name }} fileDir: './app/' encodedString: ${{ env.CI_GITHUB_KEYSTORE_KEY_FILE }} - + - name: 🔐 Getting market sign key if: ${{ startsWith(inputs.keystore-file-name, 'market_key.jks') }} uses: timheuer/base64-to-file@v1.1 From 204f2eb7f63c0302339f0d47dc1989842e5b2b60 Mon Sep 17 00:00:00 2001 From: Valentun Date: Tue, 26 Dec 2023 14:53:53 +0300 Subject: [PATCH 071/100] Remove redundant code --- .../nova/core_db/dao/MetaAccountDao.kt | 17 ----------------- .../domain/interfaces/AccountRepository.kt | 2 -- .../data/repository/AccountRepositoryImpl.kt | 5 ----- .../repository/datasource/AccountDataSource.kt | 2 -- .../datasource/AccountDataSourceImpl.kt | 4 ---- 5 files changed, 30 deletions(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 1c40315c1e..1add2cec14 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -186,23 +186,6 @@ interface MetaAccountDao { @Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)") suspend fun changeAccountsStatus(metaIds: List, status: MetaAccountLocal.Status) - - @Query( - """ - WITH RECURSIVE Sequence AS ( - SELECT pa.proxyMetaId - FROM proxy_accounts pa - WHERE pa.proxiedMetaId = :proxiedMetaId - UNION ALL - SELECT pa.proxyMetaId - FROM proxy_accounts pa - INNER JOIN Sequence s ON s.proxyMetaId = pa.proxiedMetaId - ) - SELECT MAX(proxyMetaId) as finalMetaId - FROM Sequence - """ - ) - fun getLastProxyAccountId(proxiedMetaId: Long): Long? } class MetaAccountWithBalanceLocal( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index 7bf2f91b61..eb13abb1d5 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -123,6 +123,4 @@ interface AccountRepository { ): String suspend fun isAccountExists(accountId: AccountId, chainId: ChainId): Boolean - - suspend fun getLastProxyAccountFor(proxiedMetaId: Long): MetaAccount? } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index c249c7d7c3..643f1ec8a3 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -255,11 +255,6 @@ class AccountRepositoryImpl( return accountDataSource.accountExists(accountId, chainId) } - override suspend fun getLastProxyAccountFor(proxiedMetaId: Long): MetaAccount? { - val lastProxyMetaId = accountDataSource.getLastProxyAccountId(proxiedMetaId) - return lastProxyMetaId?.let { accountDataSource.getMetaAccount(it) } - } - override fun nodesFlow(): Flow> { return nodeDao.nodesFlow() .mapList { mapNodeLocalToNode(it) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 30a06c3903..2074126099 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -91,6 +91,4 @@ interface AccountDataSource : SecretStoreV1 { ) suspend fun hasMetaAccounts(): Boolean - - suspend fun getLastProxyAccountId(proxiedMetaId: Long): Long? } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 5066f38726..f8798e9c4f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -272,10 +272,6 @@ class AccountDataSourceImpl( return metaAccountDao.hasMetaAccounts() } - override suspend fun getLastProxyAccountId(proxiedMetaId: Long): Long? { - return metaAccountDao.getLastProxyAccountId(proxiedMetaId) - } - private inline fun async(crossinline action: suspend () -> Unit) { GlobalScope.launch(Dispatchers.Default) { action() From 16f4f04ed18dd17b0be1046c40d55b4f9f78e6b1 Mon Sep 17 00:00:00 2001 From: antonijzelinskij <107959809+antonijzelinskij@users.noreply.github.com> Date: Thu, 28 Dec 2023 13:09:16 +0500 Subject: [PATCH 072/100] Fix/sync added accounts (#1291) * Refactoring adding meta accounts * Fix DI * Refactor proxySyncService * Removing deactivated proxieds * Run ktlint * Clean code * Merge conflicts --------- Co-authored-by: Valentun --- .../nova/app/root/domain/RootInteractor.kt | 2 +- .../app/root/presentation/RootViewModel.kt | 3 +- common/src/main/res/values/strings.xml | 2 +- .../nova/core_db/dao/MetaAccountDao.kt | 9 +- .../data/proxy/ProxySyncService.kt | 6 +- .../addAccount/AddAccountRepository.kt | 6 + .../addAccount/BaseAddAccountRepository.kt | 16 ++ .../ledger/LedgerAddAccountRepository.kt | 24 +++ .../di/AccountFeatureApi.kt | 3 + .../domain/interfaces/AccountInteractor.kt | 2 + .../domain/interfaces/AccountRepository.kt | 2 + .../data/mappers/Mappers.kt | 20 ++- .../data/proxy/RealProxySyncService.kt | 139 ++++++----------- .../data/repository/AccountRepositoryImpl.kt | 4 + .../data/repository/AddAccountRepository.kt | 146 ------------------ .../data/repository/WatchOnlyRepository.kt | 54 ------- .../ledger/RealLedgerAddAccountRepository.kt | 83 ++++++++++ .../ParitySignerAddAccountRepository.kt} | 40 +++-- .../proxied/ProxiedAddAccountRepository.kt | 87 +++++++++++ .../secrets/JsonAddAccountRepository.kt | 54 +++++++ .../secrets/MnemonicAddAccountRepository.kt | 38 +++++ .../secrets/SecretsAddAccountRepository.kt | 111 +++++++++++++ .../secrets/SeedAddAccountRepository.kt | 38 +++++ .../WatchOnlyAddAccountRepository.kt | 68 ++++++++ .../datasource/AccountDataSource.kt | 6 + .../datasource/AccountDataSourceImpl.kt | 4 + .../di/AccountFeatureDependencies.kt | 3 + .../di/AccountFeatureModule.kt | 42 ++--- .../di/AddAccountsModule.kt | 114 ++++++++++++++ .../di/modules/ParitySignerModule.kt | 9 -- .../domain/AccountInteractorImpl.kt | 4 + .../account/add/AddAccountInteractor.kt | 50 +++--- .../FinishImportParitySignerInteractor.kt | 12 +- .../change/ChangeWatchAccountInteractor.kt | 16 +- .../create/CreateWatchWalletInteractor.kt | 10 +- .../list/switching/SwitchWalletViewModel.kt | 8 +- .../list/switching/di/SwitchWalletModule.kt | 7 +- .../di/FinishImportParitySignerModule.kt | 6 +- .../change/di/ChangeWatchAccountModule.kt | 8 +- .../create/di/CreateWatchWalletModule.kt | 4 +- .../data/repository/LedgerDerivationPath.kt | 12 ++ .../data/repository/LedgerRepository.kt | 11 ++ .../data/repository/LedgerRepository.kt | 106 ------------- .../data/repository/RealLedgerRepository.kt | 18 +++ .../di/LedgerFeatureDependencies.kt | 3 + .../di/LedgerFeatureModule.kt | 8 +- .../AddLedgerChainAccountInteractor.kt | 12 +- .../finish/FinishImportLedgerInteractor.kt | 11 +- ...ddLedgerChainAccountSelectAddressModule.kt | 6 +- .../finish/di/FinishImportLedgerModule.kt | 6 +- .../RealSubstrateLedgerApplication.kt | 2 +- 51 files changed, 929 insertions(+), 526 deletions(-) create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/AddAccountRepository.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/BaseAddAccountRepository.kt create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/ledger/LedgerAddAccountRepository.kt delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AddAccountRepository.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/ledger/RealLedgerAddAccountRepository.kt rename feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/{ParitySignerRepository.kt => addAccount/paritySigner/ParitySignerAddAccountRepository.kt} (56%) create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/proxied/ProxiedAddAccountRepository.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/JsonAddAccountRepository.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/MnemonicAddAccountRepository.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/SecretsAddAccountRepository.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/SeedAddAccountRepository.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/watchOnly/WatchOnlyAddAccountRepository.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AddAccountsModule.kt create mode 100644 feature-ledger-api/src/main/java/io/novafoundation/nova/feature_ledger_api/data/repository/LedgerDerivationPath.kt create mode 100644 feature-ledger-api/src/main/java/io/novafoundation/nova/feature_ledger_api/data/repository/LedgerRepository.kt delete mode 100644 feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt create mode 100644 feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/RealLedgerRepository.kt diff --git a/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt b/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt index bb297b6d01..d5a3fa40a8 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt @@ -33,7 +33,7 @@ class RootInteractor( return accountRepository.isCodeSet() } - suspend fun syncProxies() { + fun syncProxies() { proxySyncService.startSyncing() } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt index 415c28c63a..eaf791122c 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt @@ -23,7 +23,6 @@ import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInt import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase import io.novafoundation.nova.feature_wallet_connect_api.presentation.WalletConnectService import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection.ExternalRequirement -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn @@ -119,7 +118,7 @@ class RootViewModel( } private fun syncProxies() { - launch(Dispatchers.Default) { interactor.syncProxies() } + interactor.syncProxies() } private fun handleUpdatesSideEffect(sideEffect: Updater.SideEffect) { diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 793d630f88..0d0d507f90 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -20,7 +20,7 @@ Nova Wallet automatically adds delegated authorities (Proxy) to a separate category for you. You can always manage wallets in Settings. What is a Proxy? - Access was revoked + No longer valid %s proxy Any diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 1add2cec14..576c933541 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -101,9 +101,6 @@ interface MetaAccountDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertProxy(proxyLocal: ProxyAccountLocal) - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertProxies(proxiesLocal: List) - @Query("SELECT * FROM meta_accounts") fun getMetaAccounts(): List @@ -111,6 +108,9 @@ interface MetaAccountDao { @Transaction suspend fun getJoinedMetaAccountsInfo(): List + @Query("SELECT * FROM meta_accounts WHERE status = :status") + fun getMetaAccountsByStatus(status: MetaAccountLocal.Status): List + @Query("SELECT * FROM meta_accounts") suspend fun getMetaAccountsInfo(): List @@ -186,6 +186,9 @@ interface MetaAccountDao { @Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)") suspend fun changeAccountsStatus(metaIds: List, status: MetaAccountLocal.Status) + + @Query("DELETE FROM meta_accounts WHERE status = :status ") + fun removeMetaAccountsByStatus(status: MetaAccountLocal.Status) } class MetaAccountWithBalanceLocal( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt index 75d61fe878..b54663ec26 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt @@ -1,10 +1,6 @@ package io.novafoundation.nova.feature_account_api.data.proxy -import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount - interface ProxySyncService { - suspend fun startSyncing() - - suspend fun syncForMetaAccount(metaAccount: MetaAccount) + fun startSyncing() } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/AddAccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/AddAccountRepository.kt new file mode 100644 index 0000000000..10ec7e29f4 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/AddAccountRepository.kt @@ -0,0 +1,6 @@ +package io.novafoundation.nova.feature_account_api.data.repository.addAccount + +interface AddAccountRepository { + + suspend fun addAccount(payload: T): Long +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/BaseAddAccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/BaseAddAccountRepository.kt new file mode 100644 index 0000000000..0c3b6a7a6a --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/BaseAddAccountRepository.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.feature_account_api.data.repository.addAccount + +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService + +abstract class BaseAddAccountRepository( + private val proxySyncService: ProxySyncService +) : AddAccountRepository { + + final override suspend fun addAccount(payload: T): Long { + val metaId = addAccountInternal(payload) + proxySyncService.startSyncing() + return metaId + } + + protected abstract suspend fun addAccountInternal(payload: T): Long +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/ledger/LedgerAddAccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/ledger/LedgerAddAccountRepository.kt new file mode 100644 index 0000000000..db227fa813 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/ledger/LedgerAddAccountRepository.kt @@ -0,0 +1,24 @@ +package io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger + +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.BaseAddAccountRepository +import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId + +abstract class LedgerAddAccountRepository( + private val proxySyncService: ProxySyncService, +) : BaseAddAccountRepository(proxySyncService) { + + sealed interface Payload { + class MetaAccount( + val name: String, + val ledgerChainAccounts: Map + ) : Payload + + class ChainAccount( + val metaId: Long, + val chainId: ChainId, + val ledgerChainAccount: LedgerSubstrateAccount + ) : Payload + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt index fad833fece..72ca04deb9 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt @@ -8,6 +8,7 @@ import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicServic import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider @@ -78,6 +79,8 @@ interface AccountFeatureApi { fun proxySyncService(): ProxySyncService + fun ledgerAddAccountRepository(): LedgerAddAccountRepository + val evmTransactionService: EvmTransactionService val identityMixinFactory: IdentityMixin.Factory diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt index 38b45aa0b6..ff33de1c27 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt @@ -57,4 +57,6 @@ interface AccountInteractor { suspend fun deleteNode(nodeId: Int) suspend fun getChainAddress(metaId: Long, chainId: ChainId): String? + + suspend fun removeDeactivatedMetaAccounts() } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index eb13abb1d5..f1bc438aef 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -123,4 +123,6 @@ interface AccountRepository { ): String suspend fun isAccountExists(accountId: AccountId, chainId: ChainId): Boolean + + suspend fun removeDeactivatedMetaAccounts() } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt index 113c5b8e90..60c027fea0 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/mappers/Mappers.kt @@ -119,13 +119,7 @@ fun mapMetaAccountLocalToMetaAccount( val chainAccounts = joinedMetaAccountInfo.chainAccounts.associateBy( keySelector = ChainAccountLocal::chainId, valueTransform = { - MetaAccount.ChainAccount( - metaId = joinedMetaAccountInfo.metaAccount.id, - publicKey = it.publicKey, - chainId = it.chainId, - accountId = it.accountId, - cryptoType = it.cryptoType - ) + mapChainAccountFromLocal(it) } ).filterNotNull() @@ -170,6 +164,18 @@ fun mapMetaAccountLocalToLightMetaAccount( } } +fun mapChainAccountFromLocal(chainAccountLocal: ChainAccountLocal): MetaAccount.ChainAccount { + return with(chainAccountLocal) { + MetaAccount.ChainAccount( + metaId = metaId, + publicKey = publicKey, + chainId = chainId, + accountId = accountId, + cryptoType = cryptoType + ) + } +} + fun mapProxyAccountFromLocal(proxyAccountLocal: ProxyAccountLocal): ProxyAccount { return with(proxyAccountLocal) { ProxyAccount( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 53db0b0289..cff285a5ad 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -4,9 +4,9 @@ import android.util.Log import io.novafoundation.nova.common.address.AccountIdKey import io.novafoundation.nova.common.address.intoKey import io.novafoundation.nova.common.utils.LOG_TAG +import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.core_db.dao.MetaAccountDao -import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy @@ -20,12 +20,14 @@ import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn -import io.novafoundation.nova.runtime.ext.addressOf +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository.Payload import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.findChains -import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull private const val SYNC_TIMEOUT = 10_000L // 10 seconds @@ -36,10 +38,18 @@ class RealProxySyncService( private val accounRepository: AccountRepository, private val accountDao: MetaAccountDao, private val identityProvider: IdentityProvider, - private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry + private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, + private val proxiedAddAccountRepository: ProxiedAddAccountRepository, + private val rootScope: RootScope ) : ProxySyncService { - override suspend fun startSyncing() { + override fun startSyncing() { + rootScope.launch(Dispatchers.Default) { + startSyncInternal() + } + } + + private suspend fun startSyncInternal() { val metaAccounts = getMetaAccounts() if (metaAccounts.isEmpty()) return @@ -50,7 +60,7 @@ class RealProxySyncService( val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> withTimeoutOrNull(SYNC_TIMEOUT) { proxyRepository.getAllProxiesForMetaAccounts(chain.id, accountIds) - } ?: emptyList() + }.orEmpty() } val oldProxies = accountDao.getAllProxyAccounts() @@ -58,18 +68,10 @@ class RealProxySyncService( val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) val identitiesByChain = notAddedProxies.loadProxiedIdentities() - val proxiedsToMetaId = notAddedProxies.map { - val proxied = it.proxied - val proxy = it.proxy - val chain = chainRegistry.getChain(proxied.chainId) - val position = accountDao.nextAccountPosition() - val identity = identitiesByChain[proxied.chainId]?.get(proxied.accountId.intoKey()) - - val proxiedMetaId = accountDao.insertProxiedMetaAccount( - metaAccount = createMetaAccount(chain, proxy.metaId, proxied.accountId, identity, position), - chainAccount = { createChainAccount(it, proxied.chainId, proxied.accountId) }, - proxyAccount = { createProxyAccount(it, proxy.metaId, proxied.chainId, proxied.accountId, proxy.proxyType) } - ) + val addedProxiedsIds = notAddedProxies.map { + val identity = identitiesByChain[it.proxied.chainId]?.get(it.proxied.accountId.intoKey()) + + val proxiedMetaId = proxiedAddAccountRepository.addAccount(Payload(it, identity)) it to proxiedMetaId } @@ -77,17 +79,13 @@ class RealProxySyncService( val deactivatedMetaAccountIds = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) accountDao.changeAccountsStatus(deactivatedMetaAccountIds, MetaAccountLocal.Status.DEACTIVATED) - val changedMetaIds = proxiedsToMetaId.map { it.second } + deactivatedMetaAccountIds + val changedMetaIds = addedProxiedsIds.map { it.second } + deactivatedMetaAccountIds metaAccountsUpdatesRegistry.addMetaIds(changedMetaIds) }.onFailure { Log.e(LOG_TAG, "Failed to sync proxy delegators", it) } } - override suspend fun syncForMetaAccount(metaAccount: MetaAccount) { - TODO("provide updater to sync proxy delegators for new added accounts") - } - private suspend fun filterNotAddedProxieds( proxiedsWithProxies: List, oldProxies: List @@ -97,12 +95,14 @@ class RealProxySyncService( } private suspend fun getDeactivatedMetaIds( - proxiedsWithProxies: List, + onChainProxies: List, oldProxies: List ): List { - val newIdentifiers = proxiedsWithProxies.map { it.toLocalIdentifier() }.toSet() - return oldProxies.filter { it.identifier !in newIdentifiers } + val newIdentifiers = onChainProxies.map { it.toLocalIdentifier() }.toSet() + val accountsToDeactivate = oldProxies.filter { it.identifier !in newIdentifiers } .map { it.proxiedMetaId } + + return accountsToDeactivate.takeNotYetDeactivatedMetaAccounts() } private suspend fun getProxiedsToRemove( @@ -117,17 +117,19 @@ class RealProxySyncService( private suspend fun getMetaAccounts(): List { return accounRepository.allMetaAccounts() - .filter { - when (it.type) { - LightMetaAccount.Type.SECRETS, - LightMetaAccount.Type.PARITY_SIGNER, - LightMetaAccount.Type.LEDGER, - LightMetaAccount.Type.POLKADOT_VAULT, - LightMetaAccount.Type.WATCH_ONLY -> true - - LightMetaAccount.Type.PROXIED -> false - } - } + .filter { it.isAllowedToSyncProxy() } + } + + private fun MetaAccount.isAllowedToSyncProxy(): Boolean { + return when (type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.LEDGER, + LightMetaAccount.Type.POLKADOT_VAULT, + LightMetaAccount.Type.WATCH_ONLY -> true + + LightMetaAccount.Type.PROXIED -> false + } } private suspend fun getSupportedProxyChains(): List { @@ -143,54 +145,6 @@ class RealProxySyncService( } } - private suspend fun createMetaAccount( - chain: Chain, - parentMetaId: Long, - proxiedAccountId: AccountId, - identity: Identity?, - position: Int - ): MetaAccountLocal { - return MetaAccountLocal( - substratePublicKey = null, - substrateCryptoType = null, - substrateAccountId = null, - ethereumPublicKey = null, - ethereumAddress = null, - name = identity?.name ?: chain.addressOf(proxiedAccountId), - parentMetaId = parentMetaId, - isSelected = false, - position = position, - type = MetaAccountLocal.Type.PROXIED, - status = MetaAccountLocal.Status.ACTIVE - ) - } - - private fun createChainAccount(metaId: Long, chainId: ChainId, accountId: AccountId): ChainAccountLocal { - return ChainAccountLocal( - metaId = metaId, - chainId = chainId, - publicKey = null, - accountId = accountId, - cryptoType = null - ) - } - - private fun createProxyAccount( - proxiedMetaId: Long, - proxyMetaId: Long, - chainId: ChainId, - proxiedAccountId: AccountId, - proxyType: String - ): ProxyAccountLocal { - return ProxyAccountLocal( - proxiedMetaId = proxiedMetaId, - proxyMetaId = proxyMetaId, - chainId = chainId, - proxiedAccountId = proxiedAccountId, - proxyType = proxyType - ) - } - private suspend fun List.loadProxiedIdentities(): Map> { return this.groupBy { it.proxied.chainId } .mapValues { (chainId, proxiedWithProxies) -> @@ -199,12 +153,19 @@ class RealProxySyncService( } } + private suspend fun List.takeNotYetDeactivatedMetaAccounts(): List { + val alreadyDeactivatedMetaAccountIds = accountDao.getMetaAccountsByStatus(MetaAccountLocal.Status.DEACTIVATED) + .mapToSet { it.id } + + return this - alreadyDeactivatedMetaAccountIds + } + private fun ProxiedWithProxy.toLocalIdentifier(): String { return ProxyAccountLocal.makeIdentifier( - proxy.metaId, - proxied.chainId, - proxied.accountId, - proxy.proxyType + proxyMetaId = proxy.metaId, + chainId = proxied.chainId, + proxiedAccountId = proxied.accountId, + proxyType = proxy.proxyType ) } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index 643f1ec8a3..161c2057ff 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -255,6 +255,10 @@ class AccountRepositoryImpl( return accountDataSource.accountExists(accountId, chainId) } + override suspend fun removeDeactivatedMetaAccounts() { + accountDataSource.removeDeactivatedMetaAccounts() + } + override fun nodesFlow(): Flow> { return nodeDao.nodesFlow() .mapList { mapNodeLocalToNode(it) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AddAccountRepository.kt deleted file mode 100644 index 0e6cae3775..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AddAccountRepository.kt +++ /dev/null @@ -1,146 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.data.repository - -import android.database.sqlite.SQLiteConstraintException -import io.novafoundation.nova.common.data.mappers.mapEncryptionToCryptoType -import io.novafoundation.nova.common.utils.removeHexPrefix -import io.novafoundation.nova.core.model.CryptoType -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountAlreadyExistsException -import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType -import io.novafoundation.nova.feature_account_api.domain.model.ImportJsonMetaData -import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource -import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory -import io.novafoundation.nova.feature_account_api.domain.account.advancedEncryption.AdvancedEncryption -import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.fearless_utils.encrypt.json.JsonSeedDecoder -import jp.co.soramitsu.fearless_utils.encrypt.model.NetworkTypeIdentifier -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class AddAccountRepository( - private val accountDataSource: AccountDataSource, - private val accountSecretsFactory: AccountSecretsFactory, - private val jsonSeedDecoder: JsonSeedDecoder, - private val chainRegistry: ChainRegistry, -) { - - suspend fun addFromMnemonic( - mnemonic: String, - advancedEncryption: AdvancedEncryption, - addAccountType: AddAccountType - ): Long = withContext(Dispatchers.Default) { - addAccount( - derivationPaths = advancedEncryption.derivationPaths, - addAccountType = addAccountType, - accountSource = AccountSecretsFactory.AccountSource.Mnemonic( - cryptoType = pickCryptoType(addAccountType, advancedEncryption), - mnemonic = mnemonic - ) - ) - } - - suspend fun addFromSeed( - seed: String, - advancedEncryption: AdvancedEncryption, - addAccountType: AddAccountType - ): Long = withContext(Dispatchers.Default) { - addAccount( - derivationPaths = advancedEncryption.derivationPaths, - addAccountType = addAccountType, - accountSource = AccountSecretsFactory.AccountSource.Seed( - cryptoType = pickCryptoType(addAccountType, advancedEncryption), - seed = seed - ) - ) - } - - suspend fun addFromJson( - json: String, - password: String, - addAccountType: AddAccountType - ): Long = withContext(Dispatchers.Default) { - addAccount( - derivationPaths = AdvancedEncryption.DerivationPaths.empty(), - addAccountType = addAccountType, - accountSource = AccountSecretsFactory.AccountSource.Json(json, password) - ) - } - - private suspend fun pickCryptoType(addAccountType: AddAccountType, advancedEncryption: AdvancedEncryption): CryptoType { - val cryptoType = if (addAccountType is AddAccountType.ChainAccount && chainRegistry.getChain(addAccountType.chainId).isEthereumBased) { - advancedEncryption.ethereumCryptoType - } else { - advancedEncryption.substrateCryptoType - } - - requireNotNull(cryptoType) { "Expected crypto type was null" } - - return cryptoType - } - - /** - * @return id of inserted/modified metaAccount - */ - private suspend fun addAccount( - derivationPaths: AdvancedEncryption.DerivationPaths, - addAccountType: AddAccountType, - accountSource: AccountSecretsFactory.AccountSource - ): Long { - return when (addAccountType) { - is AddAccountType.MetaAccount -> { - val (secrets, substrateCryptoType) = accountSecretsFactory.metaAccountSecrets( - substrateDerivationPath = derivationPaths.substrate, - ethereumDerivationPath = derivationPaths.ethereum, - accountSource = accountSource - ) - - transformingInsertionErrors { - accountDataSource.insertMetaAccountFromSecrets( - name = addAccountType.name, - substrateCryptoType = substrateCryptoType, - secrets = secrets - ) - } - } - - is AddAccountType.ChainAccount -> { - val chain = chainRegistry.getChain(addAccountType.chainId) - - val derivationPath = if (chain.isEthereumBased) derivationPaths.ethereum else derivationPaths.substrate - - val (secrets, cryptoType) = accountSecretsFactory.chainAccountSecrets( - derivationPath = derivationPath, - accountSource = accountSource, - isEthereum = chain.isEthereumBased - ) - - transformingInsertionErrors { - accountDataSource.insertChainAccount( - metaId = addAccountType.metaId, - chain = chain, - cryptoType = cryptoType, - secrets = secrets - ) - } - - addAccountType.metaId - } - } - } - - suspend fun extractJsonMetadata(importJson: String): ImportJsonMetaData = withContext(Dispatchers.Default) { - val importAccountMeta = jsonSeedDecoder.extractImportMetaData(importJson) - - with(importAccountMeta) { - val chainId = (networkTypeIdentifier as? NetworkTypeIdentifier.Genesis)?.genesis?.removeHexPrefix() - val cryptoType = mapEncryptionToCryptoType(encryption.encryptionType) - - ImportJsonMetaData(name, chainId, cryptoType) - } - } - - private inline fun transformingInsertionErrors(action: () -> R) = try { - action() - } catch (_: SQLiteConstraintException) { - throw AccountAlreadyExistsException() - } -} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt index 847b6b8242..b6de09b5ef 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/WatchOnlyRepository.kt @@ -1,10 +1,6 @@ package io.novafoundation.nova.feature_account_impl.data.repository import io.novafoundation.nova.core_db.dao.MetaAccountDao -import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal -import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import jp.co.soramitsu.fearless_utils.runtime.AccountId class WatchWalletSuggestion( val name: String, @@ -14,18 +10,6 @@ class WatchWalletSuggestion( interface WatchOnlyRepository { - suspend fun changeWatchChainAccount( - metaId: Long, - chainId: ChainId, - accountId: AccountId - ) - - suspend fun addWatchWallet( - name: String, - substrateAccountId: AccountId, - ethereumAccountId: AccountId? - ): Long - suspend fun watchWalletSuggestions(): List } @@ -33,44 +17,6 @@ class RealWatchOnlyRepository( private val accountDao: MetaAccountDao ) : WatchOnlyRepository { - override suspend fun changeWatchChainAccount( - metaId: Long, - chainId: ChainId, - accountId: AccountId - ) { - val chainAccount = ChainAccountLocal( - metaId = metaId, - chainId = chainId, - accountId = accountId, - cryptoType = null, - publicKey = null - ) - - accountDao.insertChainAccount(chainAccount) - } - - override suspend fun addWatchWallet( - name: String, - substrateAccountId: AccountId, - ethereumAccountId: AccountId? - ): Long { - val metaAccount = MetaAccountLocal( - substratePublicKey = null, - substrateCryptoType = null, - substrateAccountId = substrateAccountId, - ethereumPublicKey = null, - ethereumAddress = ethereumAccountId, - name = name, - parentMetaId = null, - isSelected = false, - position = accountDao.nextAccountPosition(), - type = MetaAccountLocal.Type.WATCH_ONLY, - status = MetaAccountLocal.Status.ACTIVE - ) - - return accountDao.insertMetaAccount(metaAccount) - } - override suspend fun watchWalletSuggestions(): List { return listOf( WatchWalletSuggestion( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/ledger/RealLedgerAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/ledger/RealLedgerAddAccountRepository.kt new file mode 100644 index 0000000000..949ea9a4d7 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/ledger/RealLedgerAddAccountRepository.kt @@ -0,0 +1,83 @@ +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.ledger + +import io.novafoundation.nova.common.data.mappers.mapEncryptionToCryptoType +import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.core_db.dao.MetaAccountDao +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository +import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerDerivationPath +import io.novafoundation.nova.runtime.ext.accountIdOf +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry + +class RealLedgerAddAccountRepository( + private val accountDao: MetaAccountDao, + private val chainRegistry: ChainRegistry, + private val secretStoreV2: SecretStoreV2, + private val proxySyncService: ProxySyncService, +) : LedgerAddAccountRepository(proxySyncService) { + + override suspend fun addAccountInternal(payload: Payload): Long { + return when (payload) { + is Payload.MetaAccount -> addMetaAccount(payload) + is Payload.ChainAccount -> addChainAccount(payload) + } + } + + private suspend fun addMetaAccount(payload: Payload.MetaAccount): Long { + val metaAccount = MetaAccountLocal( + substratePublicKey = null, + substrateCryptoType = null, + substrateAccountId = null, + ethereumPublicKey = null, + ethereumAddress = null, + name = payload.name, + parentMetaId = null, + isSelected = false, + position = accountDao.nextAccountPosition(), + type = MetaAccountLocal.Type.LEDGER, + status = MetaAccountLocal.Status.ACTIVE + ) + + val metaId = accountDao.insertMetaAndChainAccounts(metaAccount) { metaId -> + payload.ledgerChainAccounts.map { (chainId, account) -> + val chain = chainRegistry.getChain(chainId) + + ChainAccountLocal( + metaId = metaId, + chainId = chainId, + publicKey = account.publicKey, + accountId = chain.accountIdOf(account.publicKey), + cryptoType = mapEncryptionToCryptoType(account.encryptionType) + ) + } + } + + payload.ledgerChainAccounts.onEach { (chainId, ledgerAccount) -> + val derivationPathKey = LedgerDerivationPath.derivationPathSecretKey(chainId) + secretStoreV2.putAdditionalMetaAccountSecret(metaId, derivationPathKey, ledgerAccount.derivationPath) + } + + return metaId + } + + private suspend fun addChainAccount(payload: Payload.ChainAccount): Long { + val chain = chainRegistry.getChain(payload.chainId) + + val chainAccount = ChainAccountLocal( + metaId = payload.metaId, + chainId = payload.chainId, + publicKey = payload.ledgerChainAccount.publicKey, + accountId = chain.accountIdOf(payload.ledgerChainAccount.publicKey), + cryptoType = mapEncryptionToCryptoType(payload.ledgerChainAccount.encryptionType) + ) + + accountDao.insertChainAccount(chainAccount) + + val derivationPathKey = LedgerDerivationPath.derivationPathSecretKey(payload.chainId) + secretStoreV2.putAdditionalMetaAccountSecret(payload.metaId, derivationPathKey, payload.ledgerChainAccount.derivationPath) + + return payload.metaId + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/paritySigner/ParitySignerAddAccountRepository.kt similarity index 56% rename from feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt rename to feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/paritySigner/ParitySignerAddAccountRepository.kt index d2ef4d0fac..77f6a49249 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/ParitySignerRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/paritySigner/ParitySignerAddAccountRepository.kt @@ -1,41 +1,39 @@ -package io.novafoundation.nova.feature_account_impl.data.repository +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.paritySigner import io.novafoundation.nova.core.model.CryptoType import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.BaseAddAccountRepository +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.fearless_utils.runtime.AccountId -interface ParitySignerRepository { +class ParitySignerAddAccountRepository( + private val accountDao: MetaAccountDao, + private val chainRegistry: ChainRegistry, + private val proxySyncService: ProxySyncService, +) : BaseAddAccountRepository(proxySyncService) { - suspend fun addParitySignerWallet( - name: String, - substrateAccountId: AccountId, - variant: PolkadotVaultVariant, - ): Long -} - -class RealParitySignerRepository( - private val accountDao: MetaAccountDao -) : ParitySignerRepository { + class Payload( + val name: String, + val substrateAccountId: AccountId, + val variant: PolkadotVaultVariant + ) - override suspend fun addParitySignerWallet( - name: String, - substrateAccountId: AccountId, - variant: PolkadotVaultVariant - ): Long { + override suspend fun addAccountInternal(payload: Payload): Long { val metaAccount = MetaAccountLocal( // it is safe to assume that accountId is equal to public key since Parity Signer only uses SR25519 - substratePublicKey = substrateAccountId, - substrateAccountId = substrateAccountId, + substratePublicKey = payload.substrateAccountId, + substrateAccountId = payload.substrateAccountId, substrateCryptoType = CryptoType.SR25519, ethereumPublicKey = null, ethereumAddress = null, - name = name, + name = payload.name, parentMetaId = null, isSelected = false, position = accountDao.nextAccountPosition(), - type = variant.asMetaAccountTypeLocal(), + type = payload.variant.asMetaAccountTypeLocal(), status = MetaAccountLocal.Status.ACTIVE ) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/proxied/ProxiedAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/proxied/ProxiedAddAccountRepository.kt new file mode 100644 index 0000000000..29365870bf --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/proxied/ProxiedAddAccountRepository.kt @@ -0,0 +1,87 @@ +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied + +import io.novafoundation.nova.core_db.dao.MetaAccountDao +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.ProxyAccountLocal +import io.novafoundation.nova.feature_account_api.data.model.ProxiedWithProxy +import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.AddAccountRepository +import io.novafoundation.nova.runtime.ext.addressOf +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry + +/** + * It's important to extends ProxiedAddAccountRepository from AddAccountRepository instead of BaseAddAccountRepository + * since we don't need to sync proxy accounts for this case + */ +class ProxiedAddAccountRepository( + private val accountDao: MetaAccountDao, + private val chainRegistry: ChainRegistry +) : AddAccountRepository { + + class Payload( + val proxiedWithProxy: ProxiedWithProxy, + val identity: Identity? + ) + + override suspend fun addAccount(payload: Payload): Long { + val position = accountDao.nextAccountPosition() + + return accountDao.insertProxiedMetaAccount( + metaAccount = createMetaAccount(payload, position), + chainAccount = { createChainAccount(it, payload) }, + proxyAccount = { createProxyAccount(it, payload) } + ) + } + + private suspend fun createMetaAccount( + payload: Payload, + position: Int + ): MetaAccountLocal { + val proxied = payload.proxiedWithProxy.proxied + val proxy = payload.proxiedWithProxy.proxy + val chain = chainRegistry.getChain(proxied.chainId) + + return MetaAccountLocal( + substratePublicKey = null, + substrateCryptoType = null, + substrateAccountId = null, + ethereumPublicKey = null, + ethereumAddress = null, + name = payload.identity?.name ?: chain.addressOf(proxied.accountId), + parentMetaId = proxy.metaId, + isSelected = false, + position = position, + type = MetaAccountLocal.Type.PROXIED, + status = MetaAccountLocal.Status.ACTIVE + ) + } + + private fun createChainAccount(proxiedMetaId: Long, payload: Payload): ChainAccountLocal { + val proxied = payload.proxiedWithProxy.proxied + + return ChainAccountLocal( + metaId = proxiedMetaId, + chainId = proxied.chainId, + publicKey = null, + accountId = proxied.accountId, + cryptoType = null + ) + } + + private fun createProxyAccount( + proxiedMetaId: Long, + payload: Payload + ): ProxyAccountLocal { + val proxied = payload.proxiedWithProxy.proxied + val proxy = payload.proxiedWithProxy.proxy + + return ProxyAccountLocal( + proxiedMetaId = proxiedMetaId, + proxyMetaId = proxy.metaId, + chainId = proxied.chainId, + proxiedAccountId = proxied.accountId, + proxyType = proxy.proxyType + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/JsonAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/JsonAddAccountRepository.kt new file mode 100644 index 0000000000..1513108ce3 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/JsonAddAccountRepository.kt @@ -0,0 +1,54 @@ +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets + +import io.novafoundation.nova.common.data.mappers.mapEncryptionToCryptoType +import io.novafoundation.nova.common.utils.removeHexPrefix +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.domain.account.advancedEncryption.AdvancedEncryption +import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType +import io.novafoundation.nova.feature_account_api.domain.model.ImportJsonMetaData +import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource +import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.fearless_utils.encrypt.json.JsonSeedDecoder +import jp.co.soramitsu.fearless_utils.encrypt.model.NetworkTypeIdentifier +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class JsonAddAccountRepository( + private val accountDataSource: AccountDataSource, + private val accountSecretsFactory: AccountSecretsFactory, + private val jsonSeedDecoder: JsonSeedDecoder, + private val chainRegistry: ChainRegistry, + private val proxySyncService: ProxySyncService, +) : SecretsAddAccountRepository( + accountDataSource, + accountSecretsFactory, + chainRegistry, + proxySyncService +) { + + class Payload( + val json: String, + val password: String, + val addAccountType: AddAccountType + ) + + override suspend fun addAccountInternal(payload: Payload): Long { + return addSecretsAccount( + derivationPaths = AdvancedEncryption.DerivationPaths.empty(), + addAccountType = payload.addAccountType, + accountSource = AccountSecretsFactory.AccountSource.Json(payload.json, payload.password) + ) + } + + suspend fun extractJsonMetadata(importJson: String): ImportJsonMetaData = withContext(Dispatchers.Default) { + val importAccountMeta = jsonSeedDecoder.extractImportMetaData(importJson) + + with(importAccountMeta) { + val chainId = (networkTypeIdentifier as? NetworkTypeIdentifier.Genesis)?.genesis?.removeHexPrefix() + val cryptoType = mapEncryptionToCryptoType(encryption.encryptionType) + + ImportJsonMetaData(name, chainId, cryptoType) + } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/MnemonicAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/MnemonicAddAccountRepository.kt new file mode 100644 index 0000000000..56738c682c --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/MnemonicAddAccountRepository.kt @@ -0,0 +1,38 @@ +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets + +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.domain.account.advancedEncryption.AdvancedEncryption +import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType +import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource +import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry + +class MnemonicAddAccountRepository( + private val accountDataSource: AccountDataSource, + private val accountSecretsFactory: AccountSecretsFactory, + private val chainRegistry: ChainRegistry, + private val proxySyncService: ProxySyncService, +) : SecretsAddAccountRepository( + accountDataSource, + accountSecretsFactory, + chainRegistry, + proxySyncService +) { + + class Payload( + val mnemonic: String, + val advancedEncryption: AdvancedEncryption, + val addAccountType: AddAccountType + ) + + override suspend fun addAccountInternal(payload: Payload): Long { + return addSecretsAccount( + derivationPaths = payload.advancedEncryption.derivationPaths, + addAccountType = payload.addAccountType, + accountSource = AccountSecretsFactory.AccountSource.Mnemonic( + cryptoType = pickCryptoType(payload.addAccountType, payload.advancedEncryption), + mnemonic = payload.mnemonic + ) + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/SecretsAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/SecretsAddAccountRepository.kt new file mode 100644 index 0000000000..b7ff9af903 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/SecretsAddAccountRepository.kt @@ -0,0 +1,111 @@ +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets + +import android.database.sqlite.SQLiteConstraintException +import io.novafoundation.nova.core.model.CryptoType +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.BaseAddAccountRepository +import io.novafoundation.nova.feature_account_api.domain.account.advancedEncryption.AdvancedEncryption +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountAlreadyExistsException +import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType +import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource +import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +abstract class SecretsAddAccountRepository( + private val accountDataSource: AccountDataSource, + private val accountSecretsFactory: AccountSecretsFactory, + private val chainRegistry: ChainRegistry, + private val proxySyncService: ProxySyncService +) : BaseAddAccountRepository(proxySyncService) { + + class Payload( + val derivationPaths: AdvancedEncryption.DerivationPaths, + val addAccountType: AddAccountType, + val accountSource: AccountSecretsFactory.AccountSource + ) + + protected final suspend fun pickCryptoType(addAccountType: AddAccountType, advancedEncryption: AdvancedEncryption): CryptoType { + val cryptoType = if (addAccountType is AddAccountType.ChainAccount && chainRegistry.getChain(addAccountType.chainId).isEthereumBased) { + advancedEncryption.ethereumCryptoType + } else { + advancedEncryption.substrateCryptoType + } + + requireNotNull(cryptoType) { "Expected crypto type was null" } + + return cryptoType + } + + protected final suspend fun addSecretsAccount( + derivationPaths: AdvancedEncryption.DerivationPaths, + addAccountType: AddAccountType, + accountSource: AccountSecretsFactory.AccountSource + ): Long = withContext(Dispatchers.Default) { + when (addAccountType) { + is AddAccountType.MetaAccount -> { + addMetaAccount(derivationPaths, accountSource, addAccountType) + } + + is AddAccountType.ChainAccount -> { + addChainAccount(addAccountType, derivationPaths, accountSource) + } + } + } + + private suspend fun addMetaAccount( + derivationPaths: AdvancedEncryption.DerivationPaths, + accountSource: AccountSecretsFactory.AccountSource, + addAccountType: AddAccountType.MetaAccount + ): Long { + val (secrets, substrateCryptoType) = accountSecretsFactory.metaAccountSecrets( + substrateDerivationPath = derivationPaths.substrate, + ethereumDerivationPath = derivationPaths.ethereum, + accountSource = accountSource + ) + + return transformingInsertionErrors { + @Suppress("DEPRECATION") + accountDataSource.insertMetaAccountFromSecrets( + name = addAccountType.name, + substrateCryptoType = substrateCryptoType, + secrets = secrets + ) + } + } + + private suspend fun addChainAccount( + addAccountType: AddAccountType.ChainAccount, + derivationPaths: AdvancedEncryption.DerivationPaths, + accountSource: AccountSecretsFactory.AccountSource + ): Long { + val chain = chainRegistry.getChain(addAccountType.chainId) + + val derivationPath = if (chain.isEthereumBased) derivationPaths.ethereum else derivationPaths.substrate + + val (secrets, cryptoType) = accountSecretsFactory.chainAccountSecrets( + derivationPath = derivationPath, + accountSource = accountSource, + isEthereum = chain.isEthereumBased + ) + + transformingInsertionErrors { + @Suppress("DEPRECATION") + accountDataSource.insertChainAccount( + metaId = addAccountType.metaId, + chain = chain, + cryptoType = cryptoType, + secrets = secrets + ) + } + + return addAccountType.metaId + } + + private inline fun transformingInsertionErrors(action: () -> R) = try { + action() + } catch (_: SQLiteConstraintException) { + throw AccountAlreadyExistsException() + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/SeedAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/SeedAddAccountRepository.kt new file mode 100644 index 0000000000..fdafbcdc67 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/secrets/SeedAddAccountRepository.kt @@ -0,0 +1,38 @@ +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets + +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.domain.account.advancedEncryption.AdvancedEncryption +import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType +import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource +import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry + +class SeedAddAccountRepository( + private val accountDataSource: AccountDataSource, + private val accountSecretsFactory: AccountSecretsFactory, + private val chainRegistry: ChainRegistry, + private val proxySyncService: ProxySyncService, +) : SecretsAddAccountRepository( + accountDataSource, + accountSecretsFactory, + chainRegistry, + proxySyncService +) { + + class Payload( + val seed: String, + val advancedEncryption: AdvancedEncryption, + val addAccountType: AddAccountType + ) + + override suspend fun addAccountInternal(payload: Payload): Long { + return addSecretsAccount( + derivationPaths = payload.advancedEncryption.derivationPaths, + addAccountType = payload.addAccountType, + accountSource = AccountSecretsFactory.AccountSource.Seed( + cryptoType = pickCryptoType(payload.addAccountType, payload.advancedEncryption), + seed = payload.seed + ) + ) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/watchOnly/WatchOnlyAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/watchOnly/WatchOnlyAddAccountRepository.kt new file mode 100644 index 0000000000..ac772a3dbb --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/watchOnly/WatchOnlyAddAccountRepository.kt @@ -0,0 +1,68 @@ +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.watchOnly + +import io.novafoundation.nova.core_db.dao.MetaAccountDao +import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal +import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.BaseAddAccountRepository +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class WatchOnlyAddAccountRepository( + private val accountDao: MetaAccountDao, + private val proxySyncService: ProxySyncService, +) : BaseAddAccountRepository(proxySyncService) { + + sealed interface Payload { + class MetaAccount( + val name: String, + val substrateAccountId: AccountId, + val ethereumAccountId: AccountId? + ) : Payload + + class ChainAccount( + val metaId: Long, + val chainId: ChainId, + val accountId: AccountId + ) : Payload + } + + override suspend fun addAccountInternal(payload: Payload): Long { + return when (payload) { + is Payload.MetaAccount -> addWatchOnlyWallet(payload) + is Payload.ChainAccount -> changeWatchOnlyChainAccount(payload) + } + } + + private suspend fun addWatchOnlyWallet(payload: Payload.MetaAccount): Long { + val metaAccount = MetaAccountLocal( + substratePublicKey = null, + substrateCryptoType = null, + substrateAccountId = payload.substrateAccountId, + ethereumPublicKey = null, + ethereumAddress = payload.ethereumAccountId, + name = payload.name, + parentMetaId = null, + isSelected = false, + position = accountDao.nextAccountPosition(), + type = MetaAccountLocal.Type.WATCH_ONLY, + status = MetaAccountLocal.Status.ACTIVE + ) + + return accountDao.insertMetaAccount(metaAccount) + } + + private suspend fun changeWatchOnlyChainAccount(payload: Payload.ChainAccount): Long { + val chainAccount = ChainAccountLocal( + metaId = payload.metaId, + chainId = payload.chainId, + accountId = payload.accountId, + cryptoType = null, + publicKey = null + ) + + accountDao.insertChainAccount(chainAccount) + + return payload.metaId + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 2074126099..7cb2ec80b2 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -74,6 +74,8 @@ interface AccountDataSource : SecretStoreV1 { /** * @return id of inserted meta account */ + // TODO move it to SecretsAddAccountRepository + @Deprecated("Use SecretsAddAccountRepository instead") suspend fun insertMetaAccountFromSecrets( name: String, substrateCryptoType: CryptoType, @@ -83,6 +85,8 @@ interface AccountDataSource : SecretStoreV1 { /** * @return id of inserted meta account */ + // TODO move it to SecretsAddAccountRepository + @Deprecated("Use SecretsAddAccountRepository instead") suspend fun insertChainAccount( metaId: Long, chain: Chain, @@ -91,4 +95,6 @@ interface AccountDataSource : SecretStoreV1 { ) suspend fun hasMetaAccounts(): Boolean + + fun removeDeactivatedMetaAccounts() } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index f8798e9c4f..4e996e62da 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -272,6 +272,10 @@ class AccountDataSourceImpl( return metaAccountDao.hasMetaAccounts() } + override fun removeDeactivatedMetaAccounts() { + metaAccountDao.removeMetaAccountsByStatus(MetaAccountLocal.Status.DEACTIVATED) + } + private inline fun async(crossinline action: suspend () -> Unit) { GlobalScope.launch(Dispatchers.Default) { action() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt index a9a575ff6d..8848ee5d22 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureDependencies.kt @@ -22,6 +22,7 @@ import io.novafoundation.nova.common.sequrity.SafeModeService import io.novafoundation.nova.common.sequrity.TwoFactorVerificationExecutor import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService import io.novafoundation.nova.common.utils.QrCodeGenerator +import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.common.utils.systemCall.SystemCallExecutor @@ -141,4 +142,6 @@ interface AccountFeatureDependencies { val extrinsicSplitter: ExtrinsicSplitter val gasPriceProviderFactory: GasPriceProviderFactory + + val rootScope: RootScope } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index 9f2e6fc717..4600ff7711 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -18,6 +18,7 @@ import io.novafoundation.nova.common.resources.LanguagesHolder import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.biometry.BiometricServiceFactory import io.novafoundation.nova.common.utils.DEFAULT_DERIVATION_PATH +import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.common.utils.systemCall.SystemCallExecutor import io.novafoundation.nova.core.model.CryptoType import io.novafoundation.nova.core_db.dao.AccountDao @@ -55,7 +56,6 @@ import io.novafoundation.nova.feature_account_impl.data.network.blockchain.Accou import io.novafoundation.nova.feature_account_impl.data.network.blockchain.AccountSubstrateSourceImpl import io.novafoundation.nova.feature_account_impl.data.proxy.RealProxySyncService import io.novafoundation.nova.feature_account_impl.data.repository.AccountRepositoryImpl -import io.novafoundation.nova.feature_account_impl.data.repository.AddAccountRepository import io.novafoundation.nova.feature_account_impl.data.repository.RealOnChainIdentityRepository import io.novafoundation.nova.feature_account_impl.data.repository.RealProxyRepository import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource @@ -76,6 +76,10 @@ import io.novafoundation.nova.feature_account_api.domain.account.common.Encrypti import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity import io.novafoundation.nova.feature_account_impl.data.proxy.RealMetaAccountsUpdatesRegistry +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.JsonAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.MnemonicAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.SeedAddAccountRepository import io.novafoundation.nova.feature_account_impl.di.modules.ProxySigningModule import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter @@ -114,7 +118,8 @@ import jp.co.soramitsu.fearless_utils.encrypt.junction.BIP32JunctionDecoder ProxySigningModule::class, ParitySignerModule::class, IdentityProviderModule::class, - AdvancedEncryptionStoreModule::class + AdvancedEncryptionStoreModule::class, + AddAccountsModule::class ] ) class AccountFeatureModule { @@ -140,14 +145,18 @@ class AccountFeatureModule { accounRepository: AccountRepository, metaAccountDao: MetaAccountDao, @OnChainIdentity identityProvider: IdentityProvider, - metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry + metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, + proxiedAddAccountRepository: ProxiedAddAccountRepository, + rootScope: RootScope ): ProxySyncService = RealProxySyncService( chainRegistry, proxyRepository, accounRepository, metaAccountDao, identityProvider, - metaAccountsUpdatesRegistry + metaAccountsUpdatesRegistry, + proxiedAddAccountRepository, + rootScope ) @Provides @@ -326,26 +335,19 @@ class AccountFeatureModule { jsonSeedDecoder: JsonSeedDecoder ) = AccountSecretsFactory(jsonSeedDecoder) - @Provides - @FeatureScope - fun provideAddAccountRepository( - accountDataSource: AccountDataSource, - accountSecretsFactory: AccountSecretsFactory, - jsonSeedDecoder: JsonSeedDecoder, - chainRegistry: ChainRegistry, - ) = AddAccountRepository( - accountDataSource, - accountSecretsFactory, - jsonSeedDecoder, - chainRegistry - ) - @Provides @FeatureScope fun provideAddAccountInteractor( - addAccountRepository: AddAccountRepository, + mnemonicAddAccountRepository: MnemonicAddAccountRepository, + jsonAddAccountRepository: JsonAddAccountRepository, + seedAddAccountRepository: SeedAddAccountRepository, accountRepository: AccountRepository, - ) = AddAccountInteractor(addAccountRepository, accountRepository) + ) = AddAccountInteractor( + mnemonicAddAccountRepository, + jsonAddAccountRepository, + seedAddAccountRepository, + accountRepository + ) @Provides @FeatureScope diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AddAccountsModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AddAccountsModule.kt new file mode 100644 index 0000000000..ac8cc8c5f8 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AddAccountsModule.kt @@ -0,0 +1,114 @@ +package io.novafoundation.nova.feature_account_impl.di + +import dagger.Module +import dagger.Provides +import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.core_db.dao.MetaAccountDao +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource +import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFactory +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.ledger.RealLedgerAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.paritySigner.ParitySignerAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.JsonAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.MnemonicAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.SeedAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.watchOnly.WatchOnlyAddAccountRepository +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import jp.co.soramitsu.fearless_utils.encrypt.json.JsonSeedDecoder + +@Module() +class AddAccountsModule { + + @Provides + @FeatureScope + fun provideMnemonicAddAccountRepository( + accountDataSource: AccountDataSource, + accountSecretsFactory: AccountSecretsFactory, + chainRegistry: ChainRegistry, + proxySyncService: ProxySyncService + ) = MnemonicAddAccountRepository( + accountDataSource, + accountSecretsFactory, + chainRegistry, + proxySyncService + ) + + @Provides + @FeatureScope + fun provideJsonAddAccountRepository( + accountDataSource: AccountDataSource, + accountSecretsFactory: AccountSecretsFactory, + jsonSeedDecoder: JsonSeedDecoder, + chainRegistry: ChainRegistry, + proxySyncService: ProxySyncService, + ) = JsonAddAccountRepository( + accountDataSource, + accountSecretsFactory, + jsonSeedDecoder, + chainRegistry, + proxySyncService + ) + + @Provides + @FeatureScope + fun provideSeedAddAccountRepository( + accountDataSource: AccountDataSource, + accountSecretsFactory: AccountSecretsFactory, + chainRegistry: ChainRegistry, + proxySyncService: ProxySyncService, + ) = SeedAddAccountRepository( + accountDataSource, + accountSecretsFactory, + chainRegistry, + proxySyncService + ) + + @Provides + @FeatureScope + fun provideWatchOnlyAddAccountRepository( + accountDao: MetaAccountDao, + proxySyncService: ProxySyncService, + ) = WatchOnlyAddAccountRepository( + accountDao, + proxySyncService + ) + + @Provides + @FeatureScope + fun provideParitySignerAddAccountRepository( + accountDao: MetaAccountDao, + chainRegistry: ChainRegistry, + proxySyncService: ProxySyncService + ) = ParitySignerAddAccountRepository( + accountDao, + chainRegistry, + proxySyncService + ) + + @Provides + @FeatureScope + fun provideProxiedAddAccountRepository( + accountDao: MetaAccountDao, + chainRegistry: ChainRegistry + ) = ProxiedAddAccountRepository( + accountDao, + chainRegistry + ) + + @Provides + @FeatureScope + fun provideLedgerAddAccountRepository( + accountDao: MetaAccountDao, + chainRegistry: ChainRegistry, + secretStoreV2: SecretStoreV2, + proxySyncService: ProxySyncService, + ): LedgerAddAccountRepository = RealLedgerAddAccountRepository( + accountDao, + chainRegistry, + secretStoreV2, + proxySyncService + ) +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt index 3a89f1991e..fab7cee9e4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt @@ -7,9 +7,6 @@ import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.common.utils.SharedState -import io.novafoundation.nova.core_db.dao.MetaAccountDao -import io.novafoundation.nova.feature_account_impl.data.repository.ParitySignerRepository -import io.novafoundation.nova.feature_account_impl.data.repository.RealParitySignerRepository import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.common.QrCodeExpiredPresentableFactory @@ -24,12 +21,6 @@ class ParitySignerModule { mutableSharedState: MutableSharedState ): SharedState = mutableSharedState - @Provides - @FeatureScope - fun provideRepository( - accountDao: MetaAccountDao - ): ParitySignerRepository = RealParitySignerRepository(accountDao) - @Provides @FeatureScope fun provideQrCodeExpiredPresentableFactory( diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt index b71f273fe5..8aac67759a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt @@ -179,4 +179,8 @@ class AccountInteractorImpl( val chain = chainRegistry.getChain(chainId) return metaAccount.addressIn(chain) } + + override suspend fun removeDeactivatedMetaAccounts() { + accountRepository.removeDeactivatedMetaAccounts() + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/add/AddAccountInteractor.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/add/AddAccountInteractor.kt index 71a6a59dcd..5d6739c844 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/add/AddAccountInteractor.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/account/add/AddAccountInteractor.kt @@ -3,11 +3,15 @@ package io.novafoundation.nova.feature_account_impl.domain.account.add import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.AddAccountType import io.novafoundation.nova.feature_account_api.domain.model.ImportJsonMetaData -import io.novafoundation.nova.feature_account_impl.data.repository.AddAccountRepository import io.novafoundation.nova.feature_account_api.domain.account.advancedEncryption.AdvancedEncryption +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.JsonAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.MnemonicAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.SeedAddAccountRepository class AddAccountInteractor( - private val addAccountRepository: AddAccountRepository, + private val mnemonicAddAccountRepository: MnemonicAddAccountRepository, + private val jsonAddAccountRepository: JsonAddAccountRepository, + private val seedAddAccountRepository: SeedAddAccountRepository, private val accountRepository: AccountRepository, ) { @@ -17,10 +21,12 @@ class AddAccountInteractor( addAccountType: AddAccountType ): Result { return addAccount(addAccountType) { - addAccountRepository.addFromMnemonic( - mnemonic, - advancedEncryption, - addAccountType + mnemonicAddAccountRepository.addAccount( + MnemonicAddAccountRepository.Payload( + mnemonic, + advancedEncryption, + addAccountType + ) ) } } @@ -31,10 +37,12 @@ class AddAccountInteractor( addAccountType: AddAccountType ): Result { return addAccount(addAccountType) { - addAccountRepository.addFromMnemonic( - mnemonic, - advancedEncryption, - addAccountType + mnemonicAddAccountRepository.addAccount( + MnemonicAddAccountRepository.Payload( + mnemonic, + advancedEncryption, + addAccountType + ) ) } } @@ -45,10 +53,12 @@ class AddAccountInteractor( addAccountType: AddAccountType ): Result { return addAccount(addAccountType) { - addAccountRepository.addFromSeed( - seed, - advancedEncryption, - addAccountType + seedAddAccountRepository.addAccount( + SeedAddAccountRepository.Payload( + seed, + advancedEncryption, + addAccountType + ) ) } } @@ -59,10 +69,12 @@ class AddAccountInteractor( addAccountType: AddAccountType ): Result { return addAccount(addAccountType) { - addAccountRepository.addFromJson( - json = json, - password = password, - addAccountType = addAccountType + jsonAddAccountRepository.addAccount( + JsonAddAccountRepository.Payload( + json = json, + password = password, + addAccountType = addAccountType + ) ) } } @@ -77,7 +89,7 @@ class AddAccountInteractor( suspend fun extractJsonMetadata(json: String): Result { return runCatching { - addAccountRepository.extractJsonMetadata(json) + jsonAddAccountRepository.extractJsonMetadata(json) } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/paritySigner/connect/finish/FinishImportParitySignerInteractor.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/paritySigner/connect/finish/FinishImportParitySignerInteractor.kt index 27c8d7c55a..d2e1171c6d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/paritySigner/connect/finish/FinishImportParitySignerInteractor.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/paritySigner/connect/finish/FinishImportParitySignerInteractor.kt @@ -2,7 +2,7 @@ package io.novafoundation.nova.feature_account_impl.domain.paritySigner.connect. import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant -import io.novafoundation.nova.feature_account_impl.data.repository.ParitySignerRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.paritySigner.ParitySignerAddAccountRepository import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -17,7 +17,7 @@ interface FinishImportParitySignerInteractor { } class RealFinishImportParitySignerInteractor( - private val repository: ParitySignerRepository, + private val paritySignerAddAccountRepository: ParitySignerAddAccountRepository, private val accountRepository: AccountRepository, ) : FinishImportParitySignerInteractor { @@ -27,7 +27,13 @@ class RealFinishImportParitySignerInteractor( variant: PolkadotVaultVariant ): Result = withContext(Dispatchers.Default) { runCatching { - val metaId = repository.addParitySignerWallet(name, substrateAccountId, variant) + val metaId = paritySignerAddAccountRepository.addAccount( + ParitySignerAddAccountRepository.Payload( + name, + substrateAccountId, + variant + ) + ) accountRepository.selectMetaAccount(metaId) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/watchOnly/change/ChangeWatchAccountInteractor.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/watchOnly/change/ChangeWatchAccountInteractor.kt index 3979ae03a8..007f750f52 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/watchOnly/change/ChangeWatchAccountInteractor.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/watchOnly/change/ChangeWatchAccountInteractor.kt @@ -1,7 +1,6 @@ package io.novafoundation.nova.feature_account_impl.domain.watchOnly.change -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_impl.data.repository.WatchOnlyRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.watchOnly.WatchOnlyAddAccountRepository import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -15,8 +14,7 @@ interface ChangeWatchAccountInteractor { } class RealChangeWatchAccountInteractor( - private val accountRepository: AccountRepository, - private val watchOnlyRepository: WatchOnlyRepository, + private val watchOnlyAddAccountRepository: WatchOnlyAddAccountRepository ) : ChangeWatchAccountInteractor { override suspend fun changeChainAccount( @@ -26,10 +24,12 @@ class RealChangeWatchAccountInteractor( ): Result<*> = runCatching { val accountId = chain.accountIdOf(address) - watchOnlyRepository.changeWatchChainAccount( - metaId = metaId, - chainId = chain.id, - accountId = accountId + watchOnlyAddAccountRepository.addAccount( + WatchOnlyAddAccountRepository.Payload.ChainAccount( + metaId = metaId, + chainId = chain.id, + accountId = accountId + ) ) } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/watchOnly/create/CreateWatchWalletInteractor.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/watchOnly/create/CreateWatchWalletInteractor.kt index 0d6257dfba..22d4b6a9c9 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/watchOnly/create/CreateWatchWalletInteractor.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/watchOnly/create/CreateWatchWalletInteractor.kt @@ -4,6 +4,7 @@ import io.novafoundation.nova.common.utils.ethereumAddressToAccountId import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_impl.data.repository.WatchOnlyRepository import io.novafoundation.nova.feature_account_impl.data.repository.WatchWalletSuggestion +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.watchOnly.WatchOnlyAddAccountRepository import jp.co.soramitsu.fearless_utils.ss58.SS58Encoder.toAccountId interface CreateWatchWalletInteractor { @@ -19,6 +20,7 @@ interface CreateWatchWalletInteractor { class RealCreateWatchWalletInteractor( private val repository: WatchOnlyRepository, + private val watchOnlyAddAccountRepository: WatchOnlyAddAccountRepository, private val accountRepository: AccountRepository, ) : CreateWatchWalletInteractor { @@ -26,7 +28,13 @@ class RealCreateWatchWalletInteractor( val substrateAccountId = substrateAddress.toAccountId() val evmAccountId = evmAddress.takeIf { it.isNotEmpty() }?.ethereumAddressToAccountId() - val metaId = repository.addWatchWallet(name, substrateAccountId, evmAccountId) + val metaId = watchOnlyAddAccountRepository.addAccount( + WatchOnlyAddAccountRepository.Payload.MetaAccount( + name, + substrateAccountId, + evmAccountId + ) + ) accountRepository.selectMetaAccount(metaId) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletViewModel.kt index 7513a53d54..9f2ba595ee 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/SwitchWalletViewModel.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.list.switching +import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.presenatation.account.listing.items.AccountUi @@ -7,12 +8,14 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.listing. import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountWithBalanceListingMixinFactory import io.novafoundation.nova.feature_account_impl.presentation.account.list.WalletListViewModel +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class SwitchWalletViewModel( private val accountInteractor: AccountInteractor, private val router: AccountRouter, private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, + private val rootScope: RootScope, accountListingMixinFactory: MetaAccountWithBalanceListingMixinFactory, ) : WalletListViewModel() { @@ -39,6 +42,9 @@ class SwitchWalletViewModel( } fun onDestroy() { - metaAccountsUpdatesRegistry.clear() + rootScope.launch(Dispatchers.Default) { + metaAccountsUpdatesRegistry.clear() + accountInteractor.removeDeactivatedMetaAccounts() + } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/di/SwitchWalletModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/di/SwitchWalletModule.kt index 1f5b197596..3c0a2372e0 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/di/SwitchWalletModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/switching/di/SwitchWalletModule.kt @@ -8,6 +8,7 @@ import dagger.Provides import dagger.multibindings.IntoMap import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule +import io.novafoundation.nova.common.utils.coroutines.RootScope import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter @@ -24,13 +25,15 @@ class SwitchWalletModule { accountInteractor: AccountInteractor, router: AccountRouter, accountListingMixinFactory: MetaAccountWithBalanceListingMixinFactory, - metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry + metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, + rootScope: RootScope ): ViewModel { return SwitchWalletViewModel( accountInteractor = accountInteractor, router = router, accountListingMixinFactory = accountListingMixinFactory, - metaAccountsUpdatesRegistry = metaAccountsUpdatesRegistry + metaAccountsUpdatesRegistry = metaAccountsUpdatesRegistry, + rootScope = rootScope ) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/connect/finish/di/FinishImportParitySignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/connect/finish/di/FinishImportParitySignerModule.kt index d7020dd04a..64432f042e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/connect/finish/di/FinishImportParitySignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/connect/finish/di/FinishImportParitySignerModule.kt @@ -12,7 +12,7 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_impl.data.repository.ParitySignerRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.paritySigner.ParitySignerAddAccountRepository import io.novafoundation.nova.feature_account_impl.domain.paritySigner.connect.finish.FinishImportParitySignerInteractor import io.novafoundation.nova.feature_account_impl.domain.paritySigner.connect.finish.RealFinishImportParitySignerInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter @@ -25,9 +25,9 @@ class FinishImportParitySignerModule { @Provides @ScreenScope fun provideInteractor( - paritySignerRepository: ParitySignerRepository, + paritySignerAddAccountRepository: ParitySignerAddAccountRepository, accountRepository: AccountRepository - ): FinishImportParitySignerInteractor = RealFinishImportParitySignerInteractor(paritySignerRepository, accountRepository) + ): FinishImportParitySignerInteractor = RealFinishImportParitySignerInteractor(paritySignerAddAccountRepository, accountRepository) @Provides @IntoMap diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/change/di/ChangeWatchAccountModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/change/di/ChangeWatchAccountModule.kt index 2ce3bc3a7c..6ba93fb9f5 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/change/di/ChangeWatchAccountModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/change/di/ChangeWatchAccountModule.kt @@ -10,10 +10,9 @@ import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.add.AddAccountPayload import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory -import io.novafoundation.nova.feature_account_impl.data.repository.WatchOnlyRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.watchOnly.WatchOnlyAddAccountRepository import io.novafoundation.nova.feature_account_impl.domain.watchOnly.change.ChangeWatchAccountInteractor import io.novafoundation.nova.feature_account_impl.domain.watchOnly.change.RealChangeWatchAccountInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter @@ -26,9 +25,8 @@ class ChangeWatchAccountModule { @Provides @ScreenScope fun provideInteractor( - watchOnlyRepository: WatchOnlyRepository, - accountRepository: AccountRepository - ): ChangeWatchAccountInteractor = RealChangeWatchAccountInteractor(accountRepository, watchOnlyRepository) + watchOnlyAddAccountRepository: WatchOnlyAddAccountRepository + ): ChangeWatchAccountInteractor = RealChangeWatchAccountInteractor(watchOnlyAddAccountRepository) @Provides @IntoMap diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/create/di/CreateWatchWalletModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/create/di/CreateWatchWalletModule.kt index fc11f6545d..58aeb7b875 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/create/di/CreateWatchWalletModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/watchOnly/create/di/CreateWatchWalletModule.kt @@ -14,6 +14,7 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInter import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory import io.novafoundation.nova.feature_account_impl.data.repository.WatchOnlyRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.watchOnly.WatchOnlyAddAccountRepository import io.novafoundation.nova.feature_account_impl.domain.watchOnly.create.CreateWatchWalletInteractor import io.novafoundation.nova.feature_account_impl.domain.watchOnly.create.RealCreateWatchWalletInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter @@ -26,8 +27,9 @@ class CreateWatchWalletModule { @ScreenScope fun provideInteractor( watchOnlyRepository: WatchOnlyRepository, + watchOnlyAddAccountRepository: WatchOnlyAddAccountRepository, accountRepository: AccountRepository - ): CreateWatchWalletInteractor = RealCreateWatchWalletInteractor(watchOnlyRepository, accountRepository) + ): CreateWatchWalletInteractor = RealCreateWatchWalletInteractor(watchOnlyRepository, watchOnlyAddAccountRepository, accountRepository) @Provides @IntoMap diff --git a/feature-ledger-api/src/main/java/io/novafoundation/nova/feature_ledger_api/data/repository/LedgerDerivationPath.kt b/feature-ledger-api/src/main/java/io/novafoundation/nova/feature_ledger_api/data/repository/LedgerDerivationPath.kt new file mode 100644 index 0000000000..28c0fca935 --- /dev/null +++ b/feature-ledger-api/src/main/java/io/novafoundation/nova/feature_ledger_api/data/repository/LedgerDerivationPath.kt @@ -0,0 +1,12 @@ +package io.novafoundation.nova.feature_ledger_api.data.repository + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId + +object LedgerDerivationPath { + + private const val LEDGER_DERIVATION_PATH_KEY = "LedgerChainAccount.derivationPath" + + fun derivationPathSecretKey(chainId: ChainId): String { + return "$LEDGER_DERIVATION_PATH_KEY.$chainId" + } +} diff --git a/feature-ledger-api/src/main/java/io/novafoundation/nova/feature_ledger_api/data/repository/LedgerRepository.kt b/feature-ledger-api/src/main/java/io/novafoundation/nova/feature_ledger_api/data/repository/LedgerRepository.kt new file mode 100644 index 0000000000..1fdfbd2c50 --- /dev/null +++ b/feature-ledger-api/src/main/java/io/novafoundation/nova/feature_ledger_api/data/repository/LedgerRepository.kt @@ -0,0 +1,11 @@ +package io.novafoundation.nova.feature_ledger_api.data.repository + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId + +interface LedgerRepository { + + suspend fun getChainAccountDerivationPath( + metaId: Long, + chainId: ChainId + ): String +} diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt deleted file mode 100644 index e06f39c009..0000000000 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/LedgerRepository.kt +++ /dev/null @@ -1,106 +0,0 @@ -package io.novafoundation.nova.feature_ledger_impl.data.repository - -import io.novafoundation.nova.common.data.mappers.mapEncryptionToCryptoType -import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 -import io.novafoundation.nova.core_db.dao.MetaAccountDao -import io.novafoundation.nova.core_db.model.chain.account.ChainAccountLocal -import io.novafoundation.nova.core_db.model.chain.account.MetaAccountLocal -import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount -import io.novafoundation.nova.runtime.ext.accountIdOf -import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId - -interface LedgerRepository { - - suspend fun insertLedgerMetaAccount( - name: String, - ledgerChainAccounts: Map - ): Long - - suspend fun getChainAccountDerivationPath( - metaId: Long, - chainId: ChainId - ): String - - suspend fun insertLedgerChainAccount( - metaId: Long, - chainId: ChainId, - ledgerChainAccount: LedgerSubstrateAccount - ) -} - -private const val LEDGER_DERIVATION_PATH_KEY = "LedgerChainAccount.derivationPath" - -class RealLedgerRepository( - private val metaAccountDao: MetaAccountDao, - private val chainRegistry: ChainRegistry, - private val secretStoreV2: SecretStoreV2, -) : LedgerRepository { - - override suspend fun insertLedgerMetaAccount( - name: String, - ledgerChainAccounts: Map - ): Long { - val metaAccount = MetaAccountLocal( - substratePublicKey = null, - substrateCryptoType = null, - substrateAccountId = null, - ethereumPublicKey = null, - ethereumAddress = null, - name = name, - parentMetaId = null, - isSelected = false, - position = metaAccountDao.nextAccountPosition(), - type = MetaAccountLocal.Type.LEDGER, - status = MetaAccountLocal.Status.ACTIVE - ) - - val metaId = metaAccountDao.insertMetaAndChainAccounts(metaAccount) { metaId -> - ledgerChainAccounts.map { (chainId, account) -> - val chain = chainRegistry.getChain(chainId) - - ChainAccountLocal( - metaId = metaId, - chainId = chainId, - publicKey = account.publicKey, - accountId = chain.accountIdOf(account.publicKey), - cryptoType = mapEncryptionToCryptoType(account.encryptionType) - ) - } - } - - ledgerChainAccounts.onEach { (chainId, ledgerAccount) -> - val derivationPathKey = derivationPathSecretKey(chainId) - secretStoreV2.putAdditionalMetaAccountSecret(metaId, derivationPathKey, ledgerAccount.derivationPath) - } - - return metaId - } - - override suspend fun getChainAccountDerivationPath(metaId: Long, chainId: ChainId): String { - val key = derivationPathSecretKey(chainId) - - return secretStoreV2.getAdditionalMetaAccountSecret(metaId, key) - ?: throw IllegalStateException("Cannot find Ledger derivation path for chain $chainId in meta account $metaId") - } - - override suspend fun insertLedgerChainAccount(metaId: Long, chainId: ChainId, ledgerChainAccount: LedgerSubstrateAccount) { - val chain = chainRegistry.getChain(chainId) - - val chainAccount = ChainAccountLocal( - metaId = metaId, - chainId = chainId, - publicKey = ledgerChainAccount.publicKey, - accountId = chain.accountIdOf(ledgerChainAccount.publicKey), - cryptoType = mapEncryptionToCryptoType(ledgerChainAccount.encryptionType) - ) - - metaAccountDao.insertChainAccount(chainAccount) - val derivationPathKey = derivationPathSecretKey(chainId) - secretStoreV2.putAdditionalMetaAccountSecret(metaId, derivationPathKey, ledgerChainAccount.derivationPath) - } - - private fun derivationPathSecretKey(chainId: ChainId): String { - return "$LEDGER_DERIVATION_PATH_KEY.$chainId" - } -} diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/RealLedgerRepository.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/RealLedgerRepository.kt new file mode 100644 index 0000000000..62d6bfa763 --- /dev/null +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/data/repository/RealLedgerRepository.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.feature_ledger_impl.data.repository + +import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 +import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerDerivationPath +import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerRepository +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId + +class RealLedgerRepository( + private val secretStoreV2: SecretStoreV2, +) : LedgerRepository { + + override suspend fun getChainAccountDerivationPath(metaId: Long, chainId: ChainId): String { + val key = LedgerDerivationPath.derivationPathSecretKey(chainId) + + return secretStoreV2.getAdditionalMetaAccountSecret(metaId, key) + ?: throw IllegalStateException("Cannot find Ledger derivation path for chain $chainId in meta account $metaId") + } +} diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureDependencies.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureDependencies.kt index 5f230daf04..0e6f3726a9 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureDependencies.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureDependencies.kt @@ -12,6 +12,7 @@ import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager import io.novafoundation.nova.common.utils.location.LocationManager import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory import io.novafoundation.nova.core_db.dao.MetaAccountDao +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase @@ -60,4 +61,6 @@ interface LedgerFeatureDependencies { val extrinsicValidityUseCase: ExtrinsicValidityUseCase val selectedAccountUseCase: SelectedAccountUseCase + + val ledgerAddAccountRepository: LedgerAddAccountRepository } diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureModule.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureModule.kt index a360a61628..dde473b4dd 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureModule.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureModule.kt @@ -6,11 +6,10 @@ import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager -import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.SubstrateLedgerApplication import io.novafoundation.nova.feature_ledger_api.sdk.discovery.LedgerDeviceDiscoveryService import io.novafoundation.nova.feature_ledger_api.sdk.transport.LedgerTransport -import io.novafoundation.nova.feature_ledger_impl.data.repository.LedgerRepository +import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerRepository import io.novafoundation.nova.feature_ledger_impl.data.repository.RealLedgerRepository import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.RealSelectAddressLedgerInteractor import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor @@ -21,7 +20,6 @@ import io.novafoundation.nova.feature_ledger_impl.sdk.connection.ble.LedgerBleMa import io.novafoundation.nova.feature_ledger_impl.sdk.discovery.ble.BleLedgerDeviceDiscoveryService import io.novafoundation.nova.feature_ledger_impl.sdk.transport.ChunkedLedgerTransport import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry -import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @Module class LedgerFeatureModule { @@ -56,10 +54,8 @@ class LedgerFeatureModule { @Provides @FeatureScope fun provideRepository( - metaAccountDao: MetaAccountDao, - chainRegistry: ChainRegistry, secretStoreV2: SecretStoreV2 - ): LedgerRepository = RealLedgerRepository(metaAccountDao, chainRegistry, secretStoreV2) + ): LedgerRepository = RealLedgerRepository(secretStoreV2) @Provides fun provideLedgerMessagePresentable(): LedgerMessagePresentable = SingleSheetLedgerMessagePresentable() diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/addChain/AddLedgerChainAccountInteractor.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/addChain/AddLedgerChainAccountInteractor.kt index 30e8df63dd..bb3a5feea7 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/addChain/AddLedgerChainAccountInteractor.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/addChain/AddLedgerChainAccountInteractor.kt @@ -1,7 +1,7 @@ package io.novafoundation.nova.feature_ledger_impl.domain.account.addChain +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount -import io.novafoundation.nova.feature_ledger_impl.data.repository.LedgerRepository interface AddLedgerChainAccountInteractor { @@ -9,10 +9,16 @@ interface AddLedgerChainAccountInteractor { } class RealAddLedgerChainAccountInteractor( - private val ledgerRepository: LedgerRepository, + private val ledgerAddAccountRepository: LedgerAddAccountRepository ) : AddLedgerChainAccountInteractor { override suspend fun addChainAccount(metaId: Long, chainId: String, account: LedgerSubstrateAccount): Result = kotlin.runCatching { - ledgerRepository.insertLedgerChainAccount(metaId, chainId, account) + ledgerAddAccountRepository.addAccount( + LedgerAddAccountRepository.Payload.ChainAccount( + metaId, + chainId, + account + ) + ) } } diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/connect/finish/FinishImportLedgerInteractor.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/connect/finish/FinishImportLedgerInteractor.kt index 075929f155..4b84e0c793 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/connect/finish/FinishImportLedgerInteractor.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/connect/finish/FinishImportLedgerInteractor.kt @@ -1,8 +1,8 @@ package io.novafoundation.nova.feature_ledger_impl.domain.account.connect.finish +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.LedgerSubstrateAccount -import io.novafoundation.nova.feature_ledger_impl.data.repository.LedgerRepository import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId interface FinishImportLedgerInteractor { @@ -14,12 +14,17 @@ interface FinishImportLedgerInteractor { } class RealFinishImportLedgerInteractor( - private val repository: LedgerRepository, + private val ledgerAddAccountRepository: LedgerAddAccountRepository, private val accountRepository: AccountRepository, ) : FinishImportLedgerInteractor { override suspend fun createWallet(name: String, ledgerChainAccounts: Map) = runCatching { - val metaId = repository.insertLedgerMetaAccount(name, ledgerChainAccounts) + val metaId = ledgerAddAccountRepository.addAccount( + LedgerAddAccountRepository.Payload.MetaAccount( + name, + ledgerChainAccounts + ) + ) accountRepository.selectMetaAccount(metaId) } diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/addChain/selectAddress/di/AddLedgerChainAccountSelectAddressModule.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/addChain/selectAddress/di/AddLedgerChainAccountSelectAddressModule.kt index 85a17e98d8..1c9bae9d2f 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/addChain/selectAddress/di/AddLedgerChainAccountSelectAddressModule.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/addChain/selectAddress/di/AddLedgerChainAccountSelectAddressModule.kt @@ -11,7 +11,7 @@ import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.feature_ledger_impl.data.repository.LedgerRepository +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository import io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.AddLedgerChainAccountInteractor import io.novafoundation.nova.feature_ledger_impl.domain.account.addChain.RealAddLedgerChainAccountInteractor import io.novafoundation.nova.feature_ledger_impl.domain.account.common.selectAddress.SelectAddressLedgerInteractor @@ -27,8 +27,8 @@ class AddLedgerChainAccountSelectAddressModule { @Provides @ScreenScope fun provideInteractor( - repository: LedgerRepository - ): AddLedgerChainAccountInteractor = RealAddLedgerChainAccountInteractor(repository) + ledgerAddAccountRepository: LedgerAddAccountRepository + ): AddLedgerChainAccountInteractor = RealAddLedgerChainAccountInteractor(ledgerAddAccountRepository) @Provides @ScreenScope diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/connect/finish/di/FinishImportLedgerModule.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/connect/finish/di/FinishImportLedgerModule.kt index 228a7052c7..8248dc1c61 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/connect/finish/di/FinishImportLedgerModule.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/connect/finish/di/FinishImportLedgerModule.kt @@ -10,9 +10,9 @@ import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_ledger_impl.data.repository.LedgerRepository import io.novafoundation.nova.feature_ledger_impl.domain.account.connect.finish.FinishImportLedgerInteractor import io.novafoundation.nova.feature_ledger_impl.domain.account.connect.finish.RealFinishImportLedgerInteractor import io.novafoundation.nova.feature_ledger_impl.presentation.LedgerRouter @@ -25,9 +25,9 @@ class FinishImportLedgerModule { @Provides @ScreenScope fun provideInteractor( - ledgerRepository: LedgerRepository, + ledgerAddAccountRepository: LedgerAddAccountRepository, accountRepository: AccountRepository - ): FinishImportLedgerInteractor = RealFinishImportLedgerInteractor(ledgerRepository, accountRepository) + ): FinishImportLedgerInteractor = RealFinishImportLedgerInteractor(ledgerAddAccountRepository, accountRepository) @Provides @IntoMap diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/sdk/application/substrate/RealSubstrateLedgerApplication.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/sdk/application/substrate/RealSubstrateLedgerApplication.kt index 4e1df2ea14..b78f58c365 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/sdk/application/substrate/RealSubstrateLedgerApplication.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/sdk/application/substrate/RealSubstrateLedgerApplication.kt @@ -13,7 +13,7 @@ import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.Subst import io.novafoundation.nova.feature_ledger_api.sdk.device.LedgerDevice import io.novafoundation.nova.feature_ledger_api.sdk.transport.LedgerTransport import io.novafoundation.nova.feature_ledger_api.sdk.transport.send -import io.novafoundation.nova.feature_ledger_impl.data.repository.LedgerRepository +import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerRepository import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.DisplayVerificationDialog.NO import io.novafoundation.nova.feature_ledger_impl.sdk.application.substrate.DisplayVerificationDialog.YES import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId From c636c3cdfbebf71e3b59b0344c2876142bc26481 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Thu, 28 Dec 2023 11:09:34 +0300 Subject: [PATCH 073/100] Proxy/fee validation (#1294) * Add support for non-requested account fee payment in validations * Code style * Remove debug logs * Improve comments --- .../data/extrinsic/ExtrinsicService.kt | 9 ++- .../feature_account_api/data/model/Fee.kt | 28 ++++++-- .../data/signer/SignerProvider.kt | 3 +- .../domain/model/MetaAccount.kt | 16 +++-- .../transaction/RealEvmTransactionService.kt | 4 +- .../data/extrinsic/RealExtrinsicService.kt | 53 ++++++++++---- .../data/proxy/RealProxySyncService.kt | 3 +- .../data}/signer/DefaultFeeSigner.kt | 27 +++++-- .../data/signer/RealSignerProvider.kt | 6 +- .../data/signer/proxy/ProxiedFeeSigner.kt | 16 +++-- .../domain/send/SendInteractor.kt | 22 +++--- .../presentation/send/TransferDraft.kt | 2 +- .../send/amount/SelectSendViewModel.kt | 6 +- .../send/confirm/ConfirmSendViewModel.kt | 15 ++-- .../ContributeValidationsModule.kt | 2 +- .../MoonbeamTermsValidationsModule.kt | 2 +- .../ContributeValidationPayload.kt | 3 +- .../custom/moonbeam/MoonbeamTermsPayload.kt | 4 +- .../confirm/ConfirmContributeViewModel.kt | 7 +- .../parcel/ConfirmContributePayload.kt | 3 +- .../terms/MoonbeamCrowdloanTermsViewModel.kt | 14 ++-- .../select/CrowdloanContributeViewModel.kt | 71 +++++++++---------- .../domain/sign/evm/EvmSignInteractor.kt | 19 +++-- .../PolkadotExternalSignInteractor.kt | 26 +++---- .../signExtrinsic/ExternaSignViewModel.kt | 2 +- ...ChooseDelegationAmountValidationPayload.kt | 3 +- .../ChooseDelegationAmountValidationSystem.kt | 8 +-- .../RemoveVotesValidationPayload.kt | 4 +- .../RemoveVotesValidationSystem.kt | 8 +-- .../RevokeDelegationValidationPayload.kt | 4 +- .../RevokeDelegationValidationSystem.kt | 8 +-- .../unlock/GovernanceUnlockInteractor.kt | 5 +- .../UnlockReferendumValidationPayload.kt | 4 +- .../VoteReferendumValidationSystem.kt | 8 +-- .../VoteReferendumValidationPayload.kt | 3 +- .../VoteReferendumValidationSystem.kt | 8 +-- .../NewDelegationChooseAmountViewModel.kt | 7 +- .../confirm/NewDelegationConfirmPayload.kt | 3 +- .../confirm/NewDelegationConfirmViewModel.kt | 7 +- .../removeVotes/RemoveVotesViewModel.kt | 29 ++++---- .../RevokeDelegationConfirmViewModel.kt | 9 ++- .../confirm/ConfirmReferendumVoteViewModel.kt | 7 +- .../confirm/ConfirmVoteReferendumPayload.kt | 3 +- .../setup/SetupVoteReferendumViewModel.kt | 7 +- .../ConfirmGovernanceUnlockViewModel.kt | 38 +++++----- .../MakePayoutValidationsModule.kt | 4 +- .../di/validations/RebondValidationsModule.kt | 2 +- .../di/validations/RedeemValidationsModule.kt | 2 +- .../RewardDestinationValidationsModule.kt | 2 +- .../SetControllerValidationsModule.kt | 2 +- .../di/validations/UnbondValidationsModule.kt | 2 +- .../validations/RebagValidationPayload.kt | 4 +- .../validations/RebagValidationSystem.kt | 8 +-- .../validation/ProfitableActionValidation.kt | 9 ++- .../bondMore/validations/Declarations.kt | 2 +- .../claimRewards/validations/Declarations.kt | 10 +-- ...ationPoolsClaimRewardsValidationPayload.kt | 3 +- .../PoolAvailableBalanceValidation.kt | 9 ++- .../redeem/validations/Declarations.kt | 8 +-- .../NominationPoolsRedeemValidationPayload.kt | 4 +- .../unbond/validations/Declarations.kt | 10 +-- .../NominationPoolsUnbondValidationPayload.kt | 3 +- ...ParachainStakingRebondValidationPayload.kt | 4 +- .../ParachainStakingUnbondValidationSystem.kt | 2 +- ...ParachainStakingRedeemValidationPayload.kt | 4 +- .../ParachainStakingUnbondValidationSystem.kt | 2 +- .../StartParachainStakingValidationPayload.kt | 3 +- .../StartParachainStakingValidationSystem.kt | 4 +- ...ParachainStakingUnbondValidationPayload.kt | 3 +- .../ParachainStakingUnbondValidationSystem.kt | 2 +- .../validations/FirstTaskCanExecute.kt | 6 +- .../validations/ValidationSystem.kt | 8 +-- .../YieldBoostValidationPayload.kt | 4 +- .../AvailableBalanceGapValidation.kt | 3 +- .../direct/DirectStakingProperties.kt | 10 +-- .../pools/NominationPoolStakingProperties.kt | 2 +- .../ManualMultiStakingSelectionType.kt | 4 +- .../bond/BondMoreValidationPayload.kt | 3 +- .../domain/validations/bond/Declarations.kt | 4 +- .../SetControllerValidationPayload.kt | 3 +- .../validations/payout/MakePayoutPayload.kt | 3 +- .../rebond/RebondValidationPayload.kt | 3 +- .../reedeem/RedeemValidationPayload.kt | 4 +- .../RewardDestinationValidationPayload.kt | 3 +- .../domain/validations/setup/Declarations.kt | 2 +- .../validations/setup/SetupStakingPayload.kt | 4 +- .../unbond/UnbondValidationPayload.kt | 3 +- .../bagList/rebag/RebagViewModel.kt | 9 ++- ...NominationPoolsConfirmBondMoreViewModel.kt | 2 +- .../NominationPoolsClaimRewardsViewModel.kt | 5 +- .../redeem/NominationPoolsRedeemViewModel.kt | 7 +- .../NominationPoolsConfirmUnbondPayload.kt | 3 +- .../NominationPoolsConfirmUnbondViewModel.kt | 7 +- .../NominationPoolsSetupUnbondViewModel.kt | 6 +- .../rebond/ParachainStakingRebondViewModel.kt | 37 +++++----- .../redeem/ParachainStakingRedeemViewModel.kt | 37 +++++----- .../ConfirmStartParachainStakingViewModel.kt | 45 ++++++------ .../ConfirmStartParachainStakingPayload.kt | 3 +- .../setup/StartParachainStakingViewModel.kt | 52 +++++++------- .../ParachainStakingUnbondConfirmViewModel.kt | 7 +- .../ParachainStakingUnbondConfirmPayload.kt | 3 +- .../setup/ParachainStakingUnbondViewModel.kt | 48 ++++++------- .../confirm/YieldBoostConfirmViewModel.kt | 7 +- .../confirm/model/YieldBoostConfirmPayload.kt | 4 +- .../setup/SetupYieldBoostViewModel.kt | 48 ++++++------- .../payouts/confirm/ConfirmPayoutViewModel.kt | 38 +++++----- .../bond/confirm/ConfirmBondMorePayload.kt | 3 +- .../bond/confirm/ConfirmBondMoreViewModel.kt | 7 +- .../bond/select/SelectBondMoreViewModel.kt | 45 ++++++------ .../confirm/ConfirmSetControllerPayload.kt | 3 +- .../confirm/ConfirmSetControllerViewModel.kt | 7 +- .../controller/set/SetControllerViewModel.kt | 53 +++++++------- .../rebond/confirm/ConfirmRebondViewModel.kt | 34 ++++----- .../rebond/custom/CustomRebondViewModel.kt | 32 ++++----- .../staking/redeem/RedeemViewModel.kt | 39 +++++----- .../ConfirmRewardDestinationViewModel.kt | 7 +- .../parcel/ConfirmRewardDestinationPayload.kt | 4 +- .../SelectRewardDestinationViewModel.kt | 56 +++++++-------- .../confirm/ConfirmMultiStakingViewModel.kt | 2 +- .../unbond/confirm/ConfirmUnbondPayload.kt | 3 +- .../unbond/confirm/ConfirmUnbondViewModel.kt | 7 +- .../unbond/select/SelectUnbondViewModel.kt | 49 ++++++------- .../ConfirmChangeValidatorsViewModel.kt | 37 +++++----- .../domain/model/SwapQuote.kt | 3 +- .../AssetConversionExchange.kt | 7 +- .../SwapTransactionHistoryRepository.kt | 1 + .../feature_swap_impl/di/SwapFeatureModule.kt | 9 +-- .../domain/interactor/SwapInteractor.kt | 14 ++-- .../domain/swap/RealSwapService.kt | 10 ++- ...onsideringNonSufficientAssetsValidation.kt | 3 +- .../validation/SwapPayloadValidation.kt | 7 +- .../domain/validation/SwapValidations.kt | 17 +++-- ...tBalanceToPayFeeConsideringEDValidation.kt | 3 +- .../SwapFeeSufficientBalanceValidation.kt | 8 +-- .../SwapSmallRemainingBalanceValidation.kt | 6 +- .../confirmation/SwapConfirmationViewModel.kt | 6 +- .../payload/SwapConfirmationPayload.kt | 5 +- .../SwapConfirmationPayloadFormatter.kt | 15 ++-- .../main/SwapMainSettingsViewModel.kt | 5 +- .../main/SwapValidationFailureUi.kt | 21 +++--- .../feature_wallet_api/data/mappers/Fee.kt | 14 ---- .../tranfers/AssetTransferValidations.kt | 9 +-- .../EnoughAmountToTransferValidation.kt | 44 ++++++++++-- .../EnoughBalanceToStayAboveEDValidation.kt | 9 +-- .../ExistentialDepositValidation.kt | 7 +- .../domain/validation/FeeChangeValidation.kt | 29 ++++---- .../HasEnoughFreeBalanceValidation.kt | 7 +- .../domain/validation/Producers.kt | 7 +- .../presentation/mixin/fee/FeeLoaderMixin.kt | 38 ++-------- .../presentation/mixin/fee/FeeParcelModel.kt | 43 ++++++++--- .../mixin/fee/GenericFeeLoaderProvider.kt | 31 +------- .../presentation/model/FeeModel.kt | 17 +++-- .../assets/transfers/BaseAssetTransfers.kt | 2 +- .../evmErc20/EvmErc20AssetTransfers.kt | 2 +- .../evmNative/EvmNativeAssetTransfers.kt | 2 +- .../assets/transfers/validations/Common.kt | 15 ++-- .../crosschain/RealCrossChainTransactor.kt | 4 +- .../validations/CrossChainFeeValidation.kt | 7 +- .../runtime/extrinsic/signer/NovaSigner.kt | 16 +++++ 159 files changed, 1049 insertions(+), 863 deletions(-) rename {runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic => feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data}/signer/DefaultFeeSigner.kt (67%) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt index 8e2267f81a..c8597e5d99 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/extrinsic/ExtrinsicService.kt @@ -6,6 +6,7 @@ import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.Tran import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder +import io.novafoundation.nova.runtime.extrinsic.signer.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder @@ -70,11 +71,17 @@ interface ExtrinsicService { formExtrinsic: suspend ExtrinsicBuilder.() -> Unit, ): Fee + suspend fun zeroFee(chain: Chain, origin: TransactionOrigin): Fee + suspend fun estimateMultiFee( chain: Chain, origin: TransactionOrigin, formExtrinsic: FormMultiExtrinsic, ): Fee - suspend fun estimateFee(chain: Chain, extrinsic: String): Fee + suspend fun estimateFee( + chain: Chain, + extrinsic: String, + usedSigner: FeeSigner + ): Fee } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/Fee.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/Fee.kt index 7e014a4a89..f818beb1e5 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/Fee.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/Fee.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_account_api.data.model +import io.novafoundation.nova.feature_account_api.data.extrinsic.SubmissionOrigin import java.math.BigInteger interface Fee { @@ -7,13 +8,32 @@ interface Fee { companion object val amount: BigInteger + + /** + * Information about origin that is supposed to send the transaction fee was calculated against + */ + val submissionOrigin: SubmissionOrigin } -data class EvmFee(val gasLimit: BigInteger, val gasPrice: BigInteger) : Fee { +data class EvmFee( + val gasLimit: BigInteger, + val gasPrice: BigInteger, + override val submissionOrigin: SubmissionOrigin +) : Fee { override val amount = gasLimit * gasPrice } -@JvmInline -value class InlineFee(override val amount: BigInteger) : Fee +class SubstrateFee( + override val amount: BigInteger, + override val submissionOrigin: SubmissionOrigin +) : Fee + +val Fee.requestedAccountPaysFees: Boolean + get() = submissionOrigin.requestedOrigin.contentEquals(submissionOrigin.actualOrigin) -fun Fee.Companion.zero(): Fee = InlineFee(BigInteger.ZERO) +val Fee.amountByRequestedAccount: BigInteger + get() = if (requestedAccountPaysFees) { + amount + } else { + BigInteger.ZERO + } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt index df8baec5e1..33d8d3daaf 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SignerProvider.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_api.data.signer import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.runtime.extrinsic.signer.FeeSigner import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -10,5 +11,5 @@ interface SignerProvider { fun nestedSignerFor(metaAccount: MetaAccount): NovaSigner - fun feeSigner(metaAccount: MetaAccount, chain: Chain): NovaSigner + fun feeSigner(metaAccount: MetaAccount, chain: Chain): FeeSigner } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt index e029b6af49..9b22a3908d 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccount.kt @@ -185,10 +185,6 @@ private fun MultiChainEncryption.Companion.substrateFrom(cryptoType: CryptoType) fun MetaAccount.chainAccountFor(chainId: ChainId) = chainAccounts.getValue(chainId) -fun LightMetaAccount.Type.isPolkadotVaultLike(): Boolean { - return asPolkadotVaultVariantOrNull() != null -} - fun LightMetaAccount.Type.asPolkadotVaultVariantOrNull(): PolkadotVaultVariant? { return when (this) { LightMetaAccount.Type.PARITY_SIGNER -> PolkadotVaultVariant.PARITY_SIGNER @@ -202,3 +198,15 @@ fun LightMetaAccount.Type.asPolkadotVaultVariantOrThrow(): PolkadotVaultVariant "Not a Polkadot Vault compatible account type" } } + +fun LightMetaAccount.Type.requestedAccountPaysFees(): Boolean { + return when (this) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.WATCH_ONLY, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.LEDGER, + LightMetaAccount.Type.POLKADOT_VAULT -> true + + LightMetaAccount.Type.PROXIED -> false + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt index 2b15177554..284abe21fe 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/ethereum/transaction/RealEvmTransactionService.kt @@ -60,7 +60,7 @@ internal class RealEvmTransactionService( val gasPrice = gasPriceProviderFactory.createKnown(chainId).getGasPrice() val gasLimit = web3Api.gasLimitOrDefault(txForFee, fallbackGasLimit) - return EvmFee(gasLimit, gasPrice) + return EvmFee(gasLimit, gasPrice, SubmissionOrigin.singleOrigin(submittingMetaAccount.requireAccountIdIn(chain))) } override suspend fun transact( @@ -83,7 +83,7 @@ internal class RealEvmTransactionService( val gasPrice = gasPriceProviderFactory.createKnown(chainId).getGasPrice() val gasLimit = web3Api.gasLimitOrDefault(txForFee, fallbackGasLimit) - EvmFee(gasLimit, gasPrice) + EvmFee(gasLimit, gasPrice, SubmissionOrigin.singleOrigin(submittingAccountId)) } val nonce = web3Api.getNonce(submittingAddress) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt index 3b95f30626..781c97c595 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/extrinsic/RealExtrinsicService.kt @@ -4,7 +4,7 @@ import io.novafoundation.nova.common.data.network.runtime.model.FeeResponse import io.novafoundation.nova.common.utils.multiResult.RetriableMultiResult import io.novafoundation.nova.common.utils.multiResult.runMultiCatching import io.novafoundation.nova.common.utils.orZero -import io.novafoundation.nova.common.utils.sum +import io.novafoundation.nova.common.utils.sumByBigInteger import io.novafoundation.nova.common.utils.takeWhileInclusive import io.novafoundation.nova.common.utils.tip import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin @@ -15,7 +15,7 @@ import io.novafoundation.nova.feature_account_api.data.extrinsic.FormMultiExtrin import io.novafoundation.nova.feature_account_api.data.extrinsic.FormMultiExtrinsicWithOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.SubmissionOrigin import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.data.model.InlineFee +import io.novafoundation.nova.feature_account_api.data.model.SubstrateFee import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.requireMetaAccountFor @@ -25,7 +25,7 @@ import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus import io.novafoundation.nova.runtime.extrinsic.multi.ExtrinsicSplitter import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder -import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner +import io.novafoundation.nova.runtime.extrinsic.signer.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.getRuntime @@ -37,6 +37,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first +import java.math.BigInteger class RealExtrinsicService( private val rpcCalls: RpcCalls, @@ -103,14 +104,19 @@ class RealExtrinsicService( origin: TransactionOrigin, formExtrinsic: suspend ExtrinsicBuilder.() -> Unit ): Fee { - val extrinsicBuilder = extrinsicBuilderFactory.createForFee(getFeeSigner(chain, origin), chain) + val signer = getFeeSigner(chain, origin) + val extrinsicBuilder = extrinsicBuilderFactory.createForFee(signer, chain) extrinsicBuilder.formExtrinsic() val extrinsic = extrinsicBuilder.build() - return estimateFee(chain, extrinsic) + return estimateFee(chain, extrinsic, signer) } - override suspend fun estimateFee(chain: Chain, extrinsic: String): Fee { + override suspend fun estimateFee( + chain: Chain, + extrinsic: String, + usedSigner: FeeSigner + ): Fee { val chainId = chain.id val baseFee = rpcCalls.getExtrinsicFee(chain, extrinsic).partialFee @@ -120,7 +126,12 @@ class RealExtrinsicService( val tip = decodedExtrinsic.tip().orZero() - return InlineFee(tip + baseFee) + return SubstrateFee(amount = tip + baseFee, submissionOrigin = usedSigner.submissionOrigin(chain)) + } + + override suspend fun zeroFee(chain: Chain, origin: TransactionOrigin): Fee { + val signer = getFeeSigner(chain, origin) + return zeroFee(chain, signer) } override suspend fun estimateMultiFee( @@ -128,14 +139,18 @@ class RealExtrinsicService( origin: TransactionOrigin, formExtrinsic: FormMultiExtrinsic ): Fee { - val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(getFeeSigner(chain, origin), chain) + val feeSigner = getFeeSigner(chain, origin) + val feeExtrinsicBuilderSequence = extrinsicBuilderFactory.createMultiForFee(feeSigner, chain) + + val extrinsics = constructSplitExtrinsics(chain, origin, formExtrinsic, feeExtrinsicBuilderSequence, alreadyComputedFeeSigner = feeSigner) - val extrinsics = constructSplitExtrinsics(chain, origin, formExtrinsic, feeExtrinsicBuilderSequence) + if (extrinsics.isEmpty()) return zeroFee(chain, feeSigner) - val separateFees = extrinsics.map { estimateFee(chain, it).amount } - val totalFee = separateFees.sum() + val fees = extrinsics.map { estimateFee(chain, it, feeSigner) } - return InlineFee(totalFee) + val totalFee = fees.sumByBigInteger { it.amount } + + return SubstrateFee(totalFee, feeSigner.submissionOrigin(chain)) } private suspend fun constructSplitExtrinsicsForSubmission( @@ -162,11 +177,13 @@ class RealExtrinsicService( origin: TransactionOrigin, formExtrinsic: FormMultiExtrinsic, extrinsicBuilderSequence: Sequence, + alreadyComputedFeeSigner: FeeSigner? = null, ): List = coroutineScope { + val feeSigner = alreadyComputedFeeSigner ?: getFeeSigner(chain, origin) val runtime = chainRegistry.getRuntime(chain.id) val callBuilder = SimpleCallBuilder(runtime).apply { formExtrinsic() } - val splitCalls = extrinsicSplitter.split(getFeeSigner(chain, origin), callBuilder, chain) + val splitCalls = extrinsicSplitter.split(feeSigner, callBuilder, chain) val extrinsicBuilderIterator = extrinsicBuilderSequence.iterator() @@ -202,10 +219,18 @@ class RealExtrinsicService( return SubmissionRaw(extrinsic, submissionOrigin) } - private suspend fun getFeeSigner(chain: Chain, origin: TransactionOrigin): NovaSigner { + private suspend fun getFeeSigner(chain: Chain, origin: TransactionOrigin): FeeSigner { val metaAccount = accountRepository.requireMetaAccountFor(origin, chain.id) return signerProvider.feeSigner(metaAccount, chain) } private data class SubmissionRaw(val extrinsicRaw: String, val submissionOrigin: SubmissionOrigin) + + private suspend fun FeeSigner.submissionOrigin(chain: Chain): SubmissionOrigin { + return SubmissionOrigin(requestedFeeSignerId(chain = chain), actualFeeSignerId(chain)) + } + + private suspend fun zeroFee(chain: Chain, signer: FeeSigner): Fee { + return SubstrateFee(BigInteger.ZERO, signer.submissionOrigin(chain)) + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index cff285a5ad..d3beeeb557 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -55,6 +55,7 @@ class RealProxySyncService( runCatching { val supportedProxyChains = getSupportedProxyChains() + val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> @@ -105,7 +106,7 @@ class RealProxySyncService( return accountsToDeactivate.takeNotYetDeactivatedMetaAccounts() } - private suspend fun getProxiedsToRemove( + private suspend fun getPedsToRemove( oldProxies: List, proxiedsMetaAccounts: List ): List { diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/DefaultFeeSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/DefaultFeeSigner.kt similarity index 67% rename from runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/DefaultFeeSigner.kt rename to feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/DefaultFeeSigner.kt index c43a0f1181..0b08f12712 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/DefaultFeeSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/DefaultFeeSigner.kt @@ -1,12 +1,16 @@ -package io.novafoundation.nova.runtime.extrinsic.signer +package io.novafoundation.nova.feature_account_impl.data.signer +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.runtime.ext.accountIdOf +import io.novafoundation.nova.runtime.extrinsic.signer.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.encrypt.EncryptionType import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption import jp.co.soramitsu.fearless_utils.encrypt.keypair.Keypair import jp.co.soramitsu.fearless_utils.encrypt.keypair.ethereum.EthereumKeypairFactory import jp.co.soramitsu.fearless_utils.encrypt.keypair.substrate.SubstrateKeypairFactory +import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.KeyPairSigner import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw @@ -15,28 +19,39 @@ import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw private val FAKE_CRYPTO_TYPE = EncryptionType.ECDSA -class DefaultFeeSigner(private val chain: Chain) : NovaSigner { +class DefaultFeeSigner( + private val realMetaAccount: MetaAccount, + private val chain: Chain +) : FeeSigner { - private val keypair = generateFakeKeyPair() + private val fakeKeyPair = generateFakeKeyPair() override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { - val signer = KeyPairSigner(keypair, multiChainEncryption()) + val signer = KeyPairSigner(fakeKeyPair, multiChainEncryption()) return signer.signExtrinsic(payloadExtrinsic) } override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { - val signer = KeyPairSigner(keypair, multiChainEncryption()) + val signer = KeyPairSigner(fakeKeyPair, multiChainEncryption()) return signer.signRaw(payload) } + override suspend fun actualFeeSignerId(chain: Chain): AccountId { + return requestedFeeSignerId(chain) + } + + override suspend fun requestedFeeSignerId(chain: Chain): AccountId { + return realMetaAccount.requireAccountIdIn(chain) + } + override suspend fun signerAccountId(chain: Chain): ByteArray { require(chain.id == this.chain.id) { "Signer was created for the different chain, expected ${this.chain.name}, got ${chain.name}" } - return chain.accountIdOf(keypair.publicKey) + return chain.accountIdOf(fakeKeyPair.publicKey) } private fun multiChainEncryption() = if (chain.isEthereumBased) { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt index 65f0a62971..b59234f62f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/RealSignerProvider.kt @@ -9,7 +9,7 @@ import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedFeeS import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSignerFactory import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySignerFactory -import io.novafoundation.nova.runtime.extrinsic.signer.DefaultFeeSigner +import io.novafoundation.nova.runtime.extrinsic.signer.FeeSigner import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -30,13 +30,13 @@ internal class RealSignerProvider( return signerFor(metaAccount, isRoot = false) } - override fun feeSigner(metaAccount: MetaAccount, chain: Chain): NovaSigner { + override fun feeSigner(metaAccount: MetaAccount, chain: Chain): FeeSigner { return when (metaAccount.type) { LightMetaAccount.Type.SECRETS, LightMetaAccount.Type.WATCH_ONLY, LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.POLKADOT_VAULT, - LightMetaAccount.Type.LEDGER -> DefaultFeeSigner(chain) + LightMetaAccount.Type.LEDGER -> DefaultFeeSigner(metaAccount, chain) LightMetaAccount.Type.PROXIED -> proxiedFeeSignerFactory.create(metaAccount, chain, this) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt index 93ceb1bcfe..80b247b602 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxiedFeeSigner.kt @@ -6,7 +6,7 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn -import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner +import io.novafoundation.nova.runtime.extrinsic.signer.FeeSigner import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedExtrinsic @@ -34,10 +34,10 @@ class ProxiedFeeSigner( private val chain: Chain, private val signerProvider: SignerProvider, private val accountRepository: AccountRepository, -) : NovaSigner { +) : FeeSigner { private var proxyMetaAccount: MetaAccount? = null - private var delegate: NovaSigner? = null + private var delegate: FeeSigner? = null override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { val delegator = getDelegator() @@ -61,6 +61,14 @@ class ProxiedFeeSigner( return getDelegator().signRaw(payload) } + override suspend fun actualFeeSignerId(chain: Chain): AccountId { + return getDelegator().actualFeeSignerId(chain) + } + + override suspend fun requestedFeeSignerId(chain: Chain): AccountId { + return proxiedMetaAccount.requireAccountIdIn(chain) + } + override suspend fun signerAccountId(chain: Chain): AccountId { require(chain.id == this.chain.id) { "Signer was created for the different chain, expected ${this.chain.name}, got ${chain.name}" @@ -73,7 +81,7 @@ class ProxiedFeeSigner( return getProxyMetaAccount().requireAccountIdIn(chain) } - private suspend fun getDelegator(): NovaSigner { + private suspend fun getDelegator(): FeeSigner { if (delegate == null) { delegate = signerProvider.feeSigner(getProxyMetaAccount(), chain) } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/send/SendInteractor.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/send/SendInteractor.kt index 314d079fc7..38207b755d 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/send/SendInteractor.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/send/SendInteractor.kt @@ -1,8 +1,11 @@ package io.novafoundation.nova.feature_assets.domain.send import io.novafoundation.nova.common.utils.orZero +import io.novafoundation.nova.feature_account_api.data.extrinsic.SubmissionOrigin import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.data.model.InlineFee +import io.novafoundation.nova.feature_account_api.data.model.SubstrateFee +import io.novafoundation.nova.feature_account_api.data.model.amountByRequestedAccount +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfer import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.WeightedAssetTransfer @@ -14,13 +17,12 @@ import io.novafoundation.nova.feature_wallet_api.domain.implementations.transfer import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.model.CrossChainTransfersConfiguration import io.novafoundation.nova.feature_wallet_api.domain.model.RecipientSearchResult -import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.repository.ParachainInfoRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.math.BigDecimal class SendInteractor( private val walletRepository: WalletRepository, @@ -78,25 +80,29 @@ class SendInteractor( crossChainFee.reserve.orZero() + crossChainFee.destination.orZero() } - InlineFee(feePlanks) + val submissionOriginId = transfer.sender.requireAccountIdIn(transfer.originChain) + val submissionOrigin = SubmissionOrigin.singleOrigin(submissionOriginId) // cross-chain fee is always paid by requested account id + + SubstrateFee(feePlanks, submissionOrigin) } else { null } suspend fun performTransfer( transfer: WeightedAssetTransfer, - originFee: BigDecimal, - crossChainFee: BigDecimal?, + originFee: DecimalFee, + crossChainFee: DecimalFee?, ): Result<*> = withContext(Dispatchers.Default) { if (transfer.isCrossChain) { - val crossChainFeePlanks = transfer.originChainAsset.planksFromAmount(crossChainFee!!) + val crossChainFeePlanks = crossChainFee!!.networkFee.amountByRequestedAccount val config = crossChainTransfersRepository.getConfiguration().configurationFor(transfer)!! crossChainTransactor.performTransfer(config, transfer, crossChainFeePlanks) } else { getAssetTransfers(transfer).performTransfer(transfer) .onSuccess { submission -> - walletRepository.insertPendingTransfer(submission.hash, transfer, originFee) + // Insert used fee regardless of who paid it + walletRepository.insertPendingTransfer(submission.hash, transfer, originFee.networkFeeDecimalAmount) } } } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/TransferDraft.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/TransferDraft.kt index 8f4921bfab..ac49adb1cf 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/TransferDraft.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/TransferDraft.kt @@ -10,7 +10,7 @@ import java.math.BigDecimal class TransferDraft( val amount: BigDecimal, val originFee: FeeParcelModel, - val crossChainFee: BigDecimal?, + val crossChainFee: FeeParcelModel?, val origin: AssetPayload, val destination: AssetPayload, val recipientAddress: String, diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt index 4c96ebc202..c6ac601ca8 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt @@ -190,8 +190,8 @@ class SelectSendViewModel( assetTransfer = transfer, fee = originFee, ), - crossChainFee = crossChainFee?.networkFeeDecimalAmount, - originFee = originFee.networkFeeDecimalAmount, + crossChainFee = crossChainFee, + originFee = originFee, originCommissionAsset = commissionAssetFlow.first(), originUsedAsset = originAssetFlow.first() ) @@ -334,7 +334,7 @@ class SelectSendViewModel( chainAssetId = validPayload.transfer.destinationChainAsset.id ), recipientAddress = validPayload.transfer.recipient, - crossChainFee = validPayload.crossChainFee, + crossChainFee = validPayload.crossChainFee?.let(::mapFeeToParcel), openAssetDetailsOnCompletion = payload is SendPayload.SpecifiedOrigin ) diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/confirm/ConfirmSendViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/confirm/ConfirmSendViewModel.kt index 50aa5caac4..68b652068d 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/confirm/ConfirmSendViewModel.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/confirm/ConfirmSendViewModel.kt @@ -37,6 +37,7 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountSign import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.asset @@ -47,7 +48,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch -import java.math.BigDecimal class ConfirmSendChainsModel( val origin: ChainUi, @@ -75,6 +75,7 @@ class ConfirmSendViewModel( Validatable by validationExecutor { private val originFee = mapFeeFromParcel(transferDraft.originFee) + private val crossChainFee = transferDraft.crossChainFee?.let(::mapFeeFromParcel) private val originChain by lazyAsync { chainRegistry.getChain(transferDraft.origin.chainId) } private val originAsset by lazyAsync { chainRegistry.asset(transferDraft.origin.chainId, transferDraft.origin.chainAssetId) } @@ -185,8 +186,8 @@ class ConfirmSendViewModel( } private fun setInitialState() = launch { - originFeeMixin.setFee(originFee.fee) - crossChainFeeMixin.setFee(transferDraft.crossChainFee) + originFeeMixin.setFee(originFee.genericFee) + crossChainFeeMixin.setFee(crossChainFee?.genericFee) } private suspend fun createAddressModel( @@ -204,8 +205,8 @@ class ConfirmSendViewModel( private fun performTransfer( transfer: WeightedAssetTransfer, - originFee: BigDecimal, - crossChainFee: BigDecimal? + originFee: DecimalFee, + crossChainFee: DecimalFee? ) = launch { sendInteractor.performTransfer(transfer, originFee, crossChainFee) .onSuccess { @@ -246,10 +247,10 @@ class ConfirmSendViewModel( commissionAssetToken = commissionAssetFlow.first().token, decimalFee = originFee, ), - originFee = originFee.networkFeeDecimalAmount, + originFee = originFee, originCommissionAsset = commissionAssetFlow.first(), originUsedAsset = assetFlow.first(), - crossChainFee = transferDraft.crossChainFee + crossChainFee = crossChainFee ) } diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/di/validations/ContributeValidationsModule.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/di/validations/ContributeValidationsModule.kt index f635e7b590..b34007ec5d 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/di/validations/ContributeValidationsModule.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/di/validations/ContributeValidationsModule.kt @@ -28,7 +28,7 @@ class ContributeValidationsModule { feeExtractor = { it.fee }, availableBalanceProducer = { it.asset.transferable }, extraAmountExtractor = { it.contributionAmount }, - errorProducer = { _, _ -> ContributeValidationFailure.CannotPayFees } + errorProducer = { ContributeValidationFailure.CannotPayFees } ) @Provides diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/di/validations/MoonbeamTermsValidationsModule.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/di/validations/MoonbeamTermsValidationsModule.kt index b81f5583d4..7a7da1663b 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/di/validations/MoonbeamTermsValidationsModule.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/di/validations/MoonbeamTermsValidationsModule.kt @@ -19,7 +19,7 @@ class MoonbeamTermsValidationsModule { fun provideFeesValidation(): MoonbeamTermsValidation = MoonbeamTermsFeeValidation( feeExtractor = { it.fee }, availableBalanceProducer = { it.asset.transferable }, - errorProducer = { _, _ -> MoonbeamTermsValidationFailure.CANNOT_PAY_FEES } + errorProducer = { MoonbeamTermsValidationFailure.CANNOT_PAY_FEES } ) @Provides diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/validations/ContributeValidationPayload.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/validations/ContributeValidationPayload.kt index caa2080ea1..8c40bc06ee 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/validations/ContributeValidationPayload.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/validations/ContributeValidationPayload.kt @@ -4,13 +4,14 @@ import android.os.Parcelable import io.novafoundation.nova.feature_crowdloan_impl.domain.main.Crowdloan import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.BonusPayload import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal class ContributeValidationPayload( val crowdloan: Crowdloan, val customizationPayload: Parcelable?, val asset: Asset, - val fee: BigDecimal, + val fee: DecimalFee, val bonusPayload: BonusPayload?, val contributionAmount: BigDecimal, ) diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/validations/custom/moonbeam/MoonbeamTermsPayload.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/validations/custom/moonbeam/MoonbeamTermsPayload.kt index c20b75e344..02723cab9d 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/validations/custom/moonbeam/MoonbeamTermsPayload.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/domain/contribute/validations/custom/moonbeam/MoonbeamTermsPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_crowdloan_impl.domain.contribute.validations.custom.moonbeam import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class MoonbeamTermsPayload( - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset ) diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/confirm/ConfirmContributeViewModel.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/confirm/ConfirmContributeViewModel.kt index bfd6f2b3f1..c580567744 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/confirm/ConfirmContributeViewModel.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/confirm/ConfirmContributeViewModel.kt @@ -33,6 +33,7 @@ import io.novafoundation.nova.feature_wallet_api.data.mappers.mapAssetToAssetMod import io.novafoundation.nova.feature_wallet_api.data.mappers.mapFeeToFeeModel import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeStatus +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.runtime.state.SingleAssetSharedState import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.Flow @@ -58,6 +59,8 @@ class ConfirmContributeViewModel( Validatable by validationExecutor, ExternalActions by externalActions { + private val decimalFee = mapFeeFromParcel(payload.fee) + private val chain by lazyAsync { assetSharedState.chain() } override val openBrowserEvent = MutableLiveData>() @@ -82,7 +85,7 @@ class ConfirmContributeViewModel( val selectedAmount = payload.amount.toString() val feeFlow = assetFlow.map { asset -> - val feeModel = mapFeeToFeeModel(payload.fee, asset.token) + val feeModel = mapFeeToFeeModel(decimalFee.genericFee, asset.token) FeeStatus.Loaded(feeModel) } @@ -161,7 +164,7 @@ class ConfirmContributeViewModel( private fun maybeGoToNext() = launch { val validationPayload = ContributeValidationPayload( crowdloan = crowdloanFlow.first(), - fee = payload.fee, + fee = decimalFee, asset = assetFlow.first(), customizationPayload = payload.customizationPayload, bonusPayload = payload.bonusPayload, diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/confirm/parcel/ConfirmContributePayload.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/confirm/parcel/ConfirmContributePayload.kt index ee9ced8b7d..5b58438da5 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/confirm/parcel/ConfirmContributePayload.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/confirm/parcel/ConfirmContributePayload.kt @@ -4,13 +4,14 @@ import android.os.Parcelable import io.novafoundation.nova.common.data.network.runtime.binding.ParaId import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.custom.BonusPayload import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.select.parcel.ParachainMetadataParcelModel +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize import java.math.BigDecimal @Parcelize class ConfirmContributePayload( val paraId: ParaId, - val fee: BigDecimal, + val fee: FeeParcelModel, val amount: BigDecimal, val bonusPayload: BonusPayload?, val customizationPayload: Parcelable?, diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/custom/moonbeam/terms/MoonbeamCrowdloanTermsViewModel.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/custom/moonbeam/terms/MoonbeamCrowdloanTermsViewModel.kt index 244012114d..9aa1a6871a 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/custom/moonbeam/terms/MoonbeamCrowdloanTermsViewModel.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/custom/moonbeam/terms/MoonbeamCrowdloanTermsViewModel.kt @@ -20,11 +20,12 @@ import io.novafoundation.nova.feature_crowdloan_impl.presentation.contribute.sel import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.getCurrentAsset import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch -import java.math.BigDecimal sealed class SubmitActionState { object Loading : SubmitActionState() @@ -93,13 +94,15 @@ class MoonbeamCrowdloanTermsViewModel( openBrowserEvent.value = Event(interactor.getTermsLink()) } - fun submitClicked() = requireFee { fee -> + fun submitClicked() = launch { submittingInProgressFlow.value = true + val fee = feeLoaderMixin.awaitDecimalFee() + submitAfterValidation(fee) } - private fun submitAfterValidation(fee: BigDecimal) = launch { + private fun submitAfterValidation(fee: DecimalFee) = launch { val validationPayload = MoonbeamTermsPayload( fee = fee, asset = assetUseCase.getCurrentAsset() @@ -132,9 +135,4 @@ class MoonbeamCrowdloanTermsViewModel( onRetryCancelled = ::backClicked ) } - - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) } diff --git a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/select/CrowdloanContributeViewModel.kt b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/select/CrowdloanContributeViewModel.kt index 4dc1819c6c..89d25edf56 100644 --- a/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/select/CrowdloanContributeViewModel.kt +++ b/feature-crowdloan-impl/src/main/java/io/novafoundation/nova/feature_crowdloan_impl/presentation/contribute/select/CrowdloanContributeViewModel.kt @@ -39,6 +39,8 @@ import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.SimpleFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -281,47 +283,42 @@ class CrowdloanContributeViewModel( ) } - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) + private fun maybeGoToNext() = launch { + _showNextProgress.value = true - private fun maybeGoToNext() = requireFee { fee -> - launch { - val contributionAmount = parsedAmountFlow.firstOrNull() ?: return@launch + val contributionAmount = parsedAmountFlow.firstOrNull() ?: return@launch - val customizationPayload = customizationConfiguration.first()?.let { - val (_, customViewState) = it + val customizationPayload = customizationConfiguration.first()?.let { + val (_, customViewState) = it - customViewState.customizationPayloadFlow.first() - } + customViewState.customizationPayloadFlow.first() + } - val validationPayload = ContributeValidationPayload( - crowdloan = crowdloanFlow.first(), - customizationPayload = customizationPayload, - fee = fee, - asset = assetFlow.first(), - bonusPayload = router.latestCustomBonus, - contributionAmount = contributionAmount - ) + val validationPayload = ContributeValidationPayload( + crowdloan = crowdloanFlow.first(), + customizationPayload = customizationPayload, + fee = feeLoaderMixin.awaitDecimalFee(), + asset = assetFlow.first(), + bonusPayload = router.latestCustomBonus, + contributionAmount = contributionAmount + ) - validationExecutor.requireValid( - validationSystem = customizedValidationSystem.first(), - payload = validationPayload, - validationFailureTransformerCustom = { status, actions -> - contributeValidationFailure( - reason = status.reason, - validationFlowActions = actions, - resourceManager = resourceManager, - onOpenCustomContribute = ::bonusClicked - ) - }, - progressConsumer = _showNextProgress.progressConsumer() - ) { - _showNextProgress.value = false - - openConfirmScreen(it, customizationPayload) - } + validationExecutor.requireValid( + validationSystem = customizedValidationSystem.first(), + payload = validationPayload, + validationFailureTransformerCustom = { status, actions -> + contributeValidationFailure( + reason = status.reason, + validationFlowActions = actions, + resourceManager = resourceManager, + onOpenCustomContribute = ::bonusClicked + ) + }, + progressConsumer = _showNextProgress.progressConsumer() + ) { + _showNextProgress.value = false + + openConfirmScreen(it, customizationPayload) } } @@ -331,7 +328,7 @@ class CrowdloanContributeViewModel( ) = launch { val confirmContributePayload = ConfirmContributePayload( paraId = payload.paraId, - fee = validationPayload.fee, + fee = mapFeeToParcel(validationPayload.fee), amount = validationPayload.contributionAmount, estimatedRewardDisplay = estimatedRewardFlow.first(), bonusPayload = router.latestCustomBonus, diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/evm/EvmSignInteractor.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/evm/EvmSignInteractor.kt index 91cda664a0..f4090fcf22 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/evm/EvmSignInteractor.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/domain/sign/evm/EvmSignInteractor.kt @@ -13,10 +13,10 @@ import io.novafoundation.nova.common.utils.lazyAsync import io.novafoundation.nova.common.utils.parseArbitraryObject import io.novafoundation.nova.common.utils.singleReplaySharedFlow import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.feature_account_api.data.extrinsic.SubmissionOrigin import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi import io.novafoundation.nova.feature_account_api.data.model.EvmFee import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.data.model.zero import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.icon.createAccountAddressModel @@ -63,6 +63,7 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.withContext import org.web3j.crypto.RawTransaction import org.web3j.crypto.TransactionDecoder +import java.math.BigInteger class EvmSignInteractorFactory( private val chainRegistry: ChainRegistry, @@ -118,7 +119,7 @@ class EvmSignInteractor( if (payload is ConfirmTx) { checkForSimpleFeeChanges( calculateFee = { calculateFee() }, - currentFee = { it.decimalFee!!.decimalAmount }, + currentFee = { it.decimalFee }, chainAsset = { it.token!!.configuration }, error = ConfirmDAppOperationValidationFailure::FeeSpikeDetected ) @@ -159,9 +160,11 @@ class EvmSignInteractor( } override suspend fun calculateFee(): Fee = withContext(Dispatchers.Default) { - if (payload !is ConfirmTx) return@withContext Fee.zero() + if (payload !is ConfirmTx) return@withContext zeroFee() - val api = ethereumApi() ?: return@withContext Fee.zero() + resolveWalletSigner() + + val api = ethereumApi() ?: return@withContext zeroFee() val tx = api.formTransaction(payload.transaction, feeOverride = null) mostRecentFormedTx.emit(tx) @@ -195,6 +198,10 @@ class EvmSignInteractor( ethereumApi()?.shutdown() } + private fun zeroFee(): Fee { + return EvmFee(gasLimit = BigInteger.ZERO, gasPrice = BigInteger.ZERO, submissionOrigin()) + } + private suspend fun confirmTx(basedOn: EvmTransaction, upToDateFee: Fee?, evmChainId: Long, action: ConfirmTx.Action): ExternalSignCommunicator.Response { val api = requireNotNull(ethereumApi()) @@ -314,7 +321,9 @@ class EvmSignInteractor( ) } - private fun RawTransaction.fee(): Fee = EvmFee(gasLimit = gasLimit, gasPrice = gasPrice) + private fun RawTransaction.fee(): Fee = EvmFee(gasLimit = gasLimit, gasPrice = gasPrice, submissionOrigin = submissionOrigin()) + + private fun submissionOrigin() = SubmissionOrigin.singleOrigin(originAccountId()) private fun originAccountId() = payload.originAddress.asEthereumAddress().toAccountId().value 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 86e2ba1824..d7d87c6316 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 @@ -28,6 +28,8 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.Token import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.ext.utilityAsset import io.novafoundation.nova.runtime.extrinsic.CustomSignedExtensions +import io.novafoundation.nova.runtime.extrinsic.signer.FeeSigner +import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.getChainOrNull @@ -145,7 +147,8 @@ class PolkadotExternalSignInteractor( val chain = signPayload.chainOrNull() ?: return@withContext null - val extrinsicBuilder = signPayload.toExtrinsicBuilderWithoutCall(forFee = true) + val signer = signPayload.feeSigner() + val extrinsicBuilder = signPayload.toExtrinsicBuilderWithoutCall(signer) val runtime = chainRegistry.getRuntime(chain.id) val extrinsic = when (val callRepresentation = signPayload.callRepresentation(runtime)) { @@ -153,7 +156,7 @@ class PolkadotExternalSignInteractor( is CallRepresentation.Bytes -> extrinsicBuilder.build(rawCallBytes = callRepresentation.bytes) } - extrinsicService.estimateFee(chain, extrinsic) + extrinsicService.estimateFee(chain, extrinsic, signer) } private fun readableBytesContent(signBytesPayload: PolkadotSignPayload.Raw): String { @@ -183,7 +186,8 @@ class PolkadotExternalSignInteractor( private suspend fun signExtrinsic(extrinsicPayload: PolkadotSignPayload.Json): String { val runtime = chainRegistry.getRuntime(extrinsicPayload.chain().id) - val extrinsicBuilder = extrinsicPayload.toExtrinsicBuilderWithoutCall(forFee = false) + val signer = resolveWalletSigner() + val extrinsicBuilder = extrinsicPayload.toExtrinsicBuilderWithoutCall(signer) return when (val callRepresentation = extrinsicPayload.callRepresentation(runtime)) { is CallRepresentation.Instance -> extrinsicBuilder.call(callRepresentation.call).buildSignature() @@ -191,21 +195,19 @@ class PolkadotExternalSignInteractor( } } - private suspend fun PolkadotSignPayload.Json.toExtrinsicBuilderWithoutCall( - forFee: Boolean - ): ExtrinsicBuilder { + private suspend fun PolkadotSignPayload.Json.feeSigner(): FeeSigner { + val chain = chain() + + return signerProvider.feeSigner(resolveMetaAccount(), chain) + } + + private suspend fun PolkadotSignPayload.Json.toExtrinsicBuilderWithoutCall(signer: NovaSigner): ExtrinsicBuilder { val chain = chain() val runtime = chainRegistry.getRuntime(genesisHash) val parsedExtrinsic = parseDAppExtrinsic(runtime, this) val accountId = chain.accountIdOf(address) - val signer = if (forFee) { - signerProvider.feeSigner(resolveMetaAccount(), chain) - } else { - resolveWalletSigner() - } - return with(parsedExtrinsic) { ExtrinsicBuilder( runtime = runtime, diff --git a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/presentation/signExtrinsic/ExternaSignViewModel.kt b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/presentation/signExtrinsic/ExternaSignViewModel.kt index 237f2f741b..9b678e0d37 100644 --- a/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/presentation/signExtrinsic/ExternaSignViewModel.kt +++ b/feature-external-sign-impl/src/main/java/io/novafoundation/nova/feature_external_sign_impl/presentation/signExtrinsic/ExternaSignViewModel.kt @@ -112,7 +112,7 @@ class ExternaSignViewModel( autoFixPayload = ::autoFixPayload, progressConsumer = _performingOperationInProgress.progressConsumer() ) { - performOperation(it.decimalFee?.fee) + performOperation(it.decimalFee?.networkFee) } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/validation/ChooseDelegationAmountValidationPayload.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/validation/ChooseDelegationAmountValidationPayload.kt index 2a25942a3d..cc1b5200e5 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/validation/ChooseDelegationAmountValidationPayload.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/validation/ChooseDelegationAmountValidationPayload.kt @@ -1,11 +1,12 @@ package io.novafoundation.nova.feature_governance_impl.domain.delegation.delegation.create.chooseAmount.validation import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import jp.co.soramitsu.fearless_utils.runtime.AccountId import java.math.BigDecimal class ChooseDelegationAmountValidationPayload( - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset, val amount: BigDecimal, val delegate: AccountId diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/validation/ChooseDelegationAmountValidationSystem.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/validation/ChooseDelegationAmountValidationSystem.kt index e8c9445549..ee76c93548 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/validation/ChooseDelegationAmountValidationSystem.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/create/chooseAmount/validation/ChooseDelegationAmountValidationSystem.kt @@ -20,11 +20,11 @@ fun ValidationSystem.Companion.chooseDelegationAmount( sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> ChooseDelegationAmountValidationFailure.NotEnoughToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/validations/RemoveVotesValidationPayload.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/validations/RemoveVotesValidationPayload.kt index c042d58037..57e0eb1ed6 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/validations/RemoveVotesValidationPayload.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/validations/RemoveVotesValidationPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_governance_impl.domain.delegation.delegation.removeVotes.validations import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class RemoveVotesValidationPayload( - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset, ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/validations/RemoveVotesValidationSystem.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/validations/RemoveVotesValidationSystem.kt index 3fba0b1eeb..076c4245dc 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/validations/RemoveVotesValidationSystem.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/removeVotes/validations/RemoveVotesValidationSystem.kt @@ -9,11 +9,11 @@ fun ValidationSystem.Companion.removeVotesValidationSystem(): RemoteVotesValidat sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> RemoveVotesValidationFailure.NotEnoughToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/validations/RevokeDelegationValidationPayload.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/validations/RevokeDelegationValidationPayload.kt index 75ab785ca5..aecd5446f5 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/validations/RevokeDelegationValidationPayload.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/validations/RevokeDelegationValidationPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_governance_impl.domain.delegation.delegation.revoke.validations import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class RevokeDelegationValidationPayload( - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset, ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/validations/RevokeDelegationValidationSystem.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/validations/RevokeDelegationValidationSystem.kt index 2e553725c4..285a4d789b 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/validations/RevokeDelegationValidationSystem.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/delegation/delegation/revoke/validations/RevokeDelegationValidationSystem.kt @@ -9,11 +9,11 @@ fun ValidationSystem.Companion.revokeDelegationValidationSystem(): RevokeDelegat sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> RevokeDelegationValidationFailure.NotEnoughToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt index 174cb5636e..29220dcf47 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/GovernanceUnlockInteractor.kt @@ -7,7 +7,6 @@ import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.Tran import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.extrinsic.awaitInBlock import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.data.model.zero import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.OnChainReferendum @@ -81,11 +80,11 @@ class RealGovernanceUnlockInteractor( ) : GovernanceUnlockInteractor { override suspend fun calculateFee(claimable: UnlockChunk.Claimable?): Fee { - if (claimable == null) return Fee.zero() - val governanceSelectedOption = selectedAssetState.selectedOption() val chain = governanceSelectedOption.assetWithChain.chain + if (claimable == null) return extrinsicService.zeroFee(chain, TransactionOrigin.SelectedWallet) + val metaAccount = accountRepository.getSelectedMetaAccount() val origin = metaAccount.accountIdIn(chain)!! diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/validations/UnlockReferendumValidationPayload.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/validations/UnlockReferendumValidationPayload.kt index a956cb2da4..087dffe4a7 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/validations/UnlockReferendumValidationPayload.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/validations/UnlockReferendumValidationPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_governance_impl.domain.referendum.unlock.validations import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class UnlockReferendumValidationPayload( - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset, ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/validations/VoteReferendumValidationSystem.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/validations/VoteReferendumValidationSystem.kt index 8b1fd4d6c6..10dc1400fa 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/validations/VoteReferendumValidationSystem.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/unlock/validations/VoteReferendumValidationSystem.kt @@ -9,11 +9,11 @@ fun ValidationSystem.Companion.unlockReferendumValidationSystem(): UnlockReferen sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> UnlockGovernanceValidationFailure.NotEnoughToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/validations/VoteReferendumValidationPayload.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/validations/VoteReferendumValidationPayload.kt index eba157e86a..b6e0e828fc 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/validations/VoteReferendumValidationPayload.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/validations/VoteReferendumValidationPayload.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_governance_impl.domain.referendum.vote.va import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.OnChainReferendum import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.Voting import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal class VoteReferendumValidationPayload( @@ -10,5 +11,5 @@ class VoteReferendumValidationPayload( val asset: Asset, val trackVoting: Voting?, val voteAmount: BigDecimal, - val fee: BigDecimal + val fee: DecimalFee ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/validations/VoteReferendumValidationSystem.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/validations/VoteReferendumValidationSystem.kt index 387654d640..f33fd17562 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/validations/VoteReferendumValidationSystem.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/domain/referendum/vote/validations/VoteReferendumValidationSystem.kt @@ -26,11 +26,11 @@ fun ValidationSystem.Companion.voteReferendumValidationSystem( sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> VoteReferendumValidationFailure.NotEnoughToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt index e3d9073bc5..6c3cffde48 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/chooseAmount/NewDelegationChooseAmountViewModel.kt @@ -34,6 +34,7 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeL import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first @@ -157,10 +158,10 @@ class NewDelegationChooseAmountViewModel( private fun openConfirmIfValid() = launch { validationInProgressFlow.value = true - val fee = originFeeMixin.awaitDecimalFee().networkFeeDecimalAmount + val payload = ChooseDelegationAmountValidationPayload( asset = selectedAsset.first(), - fee = fee, + fee = originFeeMixin.awaitDecimalFee(), amount = amountChooserMixin.amount.first(), delegate = payload.delegate ) @@ -183,7 +184,7 @@ class NewDelegationChooseAmountViewModel( trackIdsRaw = payload.trackIdsRaw, amount = validationPayload.amount, conviction = selectedConvictionFlow.first(), - fee = validationPayload.fee, + fee = mapFeeToParcel(validationPayload.fee), isEditMode = payload.isEditMode ) diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/confirm/NewDelegationConfirmPayload.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/confirm/NewDelegationConfirmPayload.kt index 44061442cb..3d228b07a9 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/confirm/NewDelegationConfirmPayload.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/confirm/NewDelegationConfirmPayload.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_governance_impl.presentation.delegation.d import android.os.Parcelable import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.TrackId import io.novafoundation.nova.feature_governance_api.domain.referendum.voters.GenericVoter +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.android.parcel.Parcelize @@ -15,7 +16,7 @@ class NewDelegationConfirmPayload( @Suppress("CanBeParameter") val trackIdsRaw: List, val amount: BigDecimal, val conviction: Conviction, - val fee: BigDecimal, + val fee: FeeParcelModel, val isEditMode: Boolean, ) : Parcelable { diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/confirm/NewDelegationConfirmViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/confirm/NewDelegationConfirmViewModel.kt index 525ba5bc7c..37b3eb9e35 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/confirm/NewDelegationConfirmViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/confirm/NewDelegationConfirmViewModel.kt @@ -43,6 +43,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.chain import io.novafoundation.nova.runtime.state.chainAsset @@ -138,6 +139,8 @@ class NewDelegationConfirmViewModel( private val _showTracksEvent = MutableLiveData>>() val showTracksEvent: LiveData>> = _showTracksEvent + private val decimalFee = mapFeeFromParcel(payload.fee) + init { setFee() } @@ -166,7 +169,7 @@ class NewDelegationConfirmViewModel( val amountPlanks = asset.token.planksFromAmount(payload.amount) val validationPayload = ChooseDelegationAmountValidationPayload( asset = asset, - fee = payload.fee, + fee = decimalFee, amount = payload.amount, delegate = payload.delegate ) @@ -186,7 +189,7 @@ class NewDelegationConfirmViewModel( } private fun setFee() = launch { - originFeeMixin.setFee(payload.fee) + originFeeMixin.setFee(decimalFee.genericFee) } private fun performDelegate(amountPlanks: Balance) = launch { diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/removeVotes/RemoveVotesViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/removeVotes/RemoveVotesViewModel.kt index 47c2afd542..61f3b5a42e 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/removeVotes/RemoveVotesViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/removeVotes/RemoveVotesViewModel.kt @@ -27,8 +27,8 @@ import io.novafoundation.nova.feature_governance_impl.presentation.track.formatT import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.requireFee import io.novafoundation.nova.runtime.state.chain import io.novafoundation.nova.runtime.state.chainAsset import kotlinx.coroutines.flow.Flow @@ -103,18 +103,21 @@ class RemoveVotesViewModel( externalActions.showExternalActions(type, governanceSharedState.chain()) } - private fun removeVotesIfValid(): Unit = originFeeMixin.requireFee(viewModel = this) { fee -> - launch { - val validationPayload = RemoveVotesValidationPayload(fee, assetFlow.first()) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = validationPayload, - progressConsumer = _showNextProgress.progressConsumer(), - validationFailureTransformer = { handleRemoveVotesValidationFailure(it, resourceManager) } - ) { - removeVotes() - } + private fun removeVotesIfValid() = launch { + _showNextProgress.value = true + + val validationPayload = RemoveVotesValidationPayload( + fee = originFeeMixin.awaitDecimalFee(), + asset = assetFlow.first() + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = validationPayload, + progressConsumer = _showNextProgress.progressConsumer(), + validationFailureTransformer = { handleRemoveVotesValidationFailure(it, resourceManager) } + ) { + removeVotes() } } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt index b0ec2677ac..f63e39812e 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/revoke/confirm/RevokeDelegationConfirmViewModel.kt @@ -35,6 +35,7 @@ import io.novafoundation.nova.feature_governance_impl.presentation.track.TrackFo import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import io.novafoundation.nova.runtime.state.chain import io.novafoundation.nova.runtime.state.chainAsset @@ -146,10 +147,12 @@ class RevokeDelegationConfirmViewModel( } fun confirmClicked() = launch { - val asset = assetFlow.first() - val fee = originFeeMixin.awaitFee() + _showNextProgress.value = true - val validationPayload = RevokeDelegationValidationPayload(fee, asset) + val validationPayload = RevokeDelegationValidationPayload( + fee = originFeeMixin.awaitDecimalFee(), + asset = assetFlow.first() + ) validationExecutor.requireValid( validationSystem = validationSystem, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/confirm/ConfirmReferendumVoteViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/confirm/ConfirmReferendumVoteViewModel.kt index 238e229819..70a9cc1b96 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/confirm/ConfirmReferendumVoteViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/confirm/ConfirmReferendumVoteViewModel.kt @@ -32,6 +32,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Vote import io.novafoundation.nova.runtime.state.chain @@ -120,6 +121,8 @@ class ConfirmReferendumVoteViewModel( } .shareInBackground() + private val decimalFee = mapFeeFromParcel(payload.fee) + init { setFee() } @@ -139,7 +142,7 @@ class ConfirmReferendumVoteViewModel( asset = assetFlow.first(), trackVoting = voteAssistant.trackVoting, voteAmount = payload.vote.amount, - fee = payload.fee + fee = decimalFee ) validationExecutor.requireValid( @@ -157,7 +160,7 @@ class ConfirmReferendumVoteViewModel( } private fun setFee() = launch { - originFeeMixin.setFee(payload.fee) + originFeeMixin.setFee(decimalFee.genericFee) } private fun performVote() = launch { diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/confirm/ConfirmVoteReferendumPayload.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/confirm/ConfirmVoteReferendumPayload.kt index 80b93958eb..b23fdac54d 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/confirm/ConfirmVoteReferendumPayload.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/confirm/ConfirmVoteReferendumPayload.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_governance_impl.presentation.referenda.vo import android.os.Parcelable import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction import kotlinx.android.parcel.Parcelize import java.math.BigDecimal @@ -10,7 +11,7 @@ import java.math.BigInteger @Parcelize class ConfirmVoteReferendumPayload( val _referendumId: BigInteger, - val fee: BigDecimal, + val fee: FeeParcelModel, val vote: AccountVoteParcelModel ) : Parcelable diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/SetupVoteReferendumViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/SetupVoteReferendumViewModel.kt index 05c65f15a9..6fbfc2ef27 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/SetupVoteReferendumViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/vote/setup/SetupVoteReferendumViewModel.kt @@ -33,8 +33,10 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChoose import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.setAmountInput import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first @@ -156,7 +158,6 @@ class SetupVoteReferendumViewModel( private fun openConfirmIfValid(voteType: VoteType) = launch { validatingVoteType.value = voteType - val fee = originFeeMixin.awaitFee() val voteAssistant = voteAssistantFlow.first() val payload = VoteReferendumValidationPayload( @@ -164,7 +165,7 @@ class SetupVoteReferendumViewModel( asset = selectedAsset.first(), trackVoting = voteAssistant.trackVoting, voteAmount = amountChooserMixin.amount.first(), - fee = fee + fee = originFeeMixin.awaitDecimalFee() ) validationExecutor.requireValid( @@ -184,7 +185,7 @@ class SetupVoteReferendumViewModel( val confirmPayload = ConfirmVoteReferendumPayload( _referendumId = payload._referendumId, - fee = validationPayload.fee, + fee = mapFeeToParcel(validationPayload.fee), vote = AccountVoteParcelModel( amount = validationPayload.voteAmount, conviction = conviction, diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/confirm/ConfirmGovernanceUnlockViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/confirm/ConfirmGovernanceUnlockViewModel.kt index 62db89d046..00de568851 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/confirm/ConfirmGovernanceUnlockViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/unlock/confirm/ConfirmGovernanceUnlockViewModel.kt @@ -30,9 +30,9 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Ba import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.requireFee import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.Flow @@ -135,24 +135,24 @@ class ConfirmGovernanceUnlockViewModel( router.back() } - fun confirmClicked() = originFeeMixin.requireFee(this) { fee -> - launch { - val claimable = unlockAffectsFlow.first().claimableChunk - val locksChange = unlockAffectsFlow.first().governanceLockChange - - val validationPayload = UnlockReferendumValidationPayload( - asset = assetFlow.first(), - fee = fee - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = validationPayload, - validationFailureTransformer = { handleUnlockReferendumValidationFailure(it, resourceManager) }, - progressConsumer = submissionInProgress.progressConsumer(), - ) { - executeUnlock(claimable, locksChange) - } + fun confirmClicked() = launch { + submissionInProgress.value = true + + val claimable = unlockAffectsFlow.first().claimableChunk + val locksChange = unlockAffectsFlow.first().governanceLockChange + + val validationPayload = UnlockReferendumValidationPayload( + asset = assetFlow.first(), + fee = originFeeMixin.awaitDecimalFee() + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = validationPayload, + validationFailureTransformer = { handleUnlockReferendumValidationFailure(it, resourceManager) }, + progressConsumer = submissionInProgress.progressConsumer(), + ) { + executeUnlock(claimable, locksChange) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/MakePayoutValidationsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/MakePayoutValidationsModule.kt index 548537ea01..abeefe5806 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/MakePayoutValidationsModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/MakePayoutValidationsModule.kt @@ -22,14 +22,14 @@ class MakePayoutValidationsModule { return EnoughAmountToTransferValidation( feeExtractor = { it.fee }, availableBalanceProducer = { it.asset.transferable }, - errorProducer = { _, _ -> PayoutValidationFailure.CannotPayFee } + errorProducer = { PayoutValidationFailure.CannotPayFee } ) } @FeatureScope @Provides fun provideProfitableValidation() = ProfitablePayoutValidation( - fee = { fee }, + fee = { it.fee }, amount = { totalReward }, error = { PayoutValidationFailure.UnprofitablePayout } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RebondValidationsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RebondValidationsModule.kt index 161befea9c..d507c46358 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RebondValidationsModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RebondValidationsModule.kt @@ -18,7 +18,7 @@ class RebondValidationsModule { fun provideFeeValidation() = RebondFeeValidation( feeExtractor = { it.fee }, availableBalanceProducer = { it.controllerAsset.transferable }, - errorProducer = { _, _ -> RebondValidationFailure.CANNOT_PAY_FEE } + errorProducer = { RebondValidationFailure.CANNOT_PAY_FEE } ) @FeatureScope diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RedeemValidationsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RedeemValidationsModule.kt index ddf87fd537..90517a70aa 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RedeemValidationsModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RedeemValidationsModule.kt @@ -16,7 +16,7 @@ class RedeemValidationsModule { fun provideFeeValidation() = RedeemFeeValidation( feeExtractor = { it.fee }, availableBalanceProducer = { it.asset.transferable }, - errorProducer = { _, _ -> RedeemValidationFailure.CANNOT_PAY_FEES } + errorProducer = { RedeemValidationFailure.CANNOT_PAY_FEES } ) @FeatureScope diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RewardDestinationValidationsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RewardDestinationValidationsModule.kt index ac22da1cc0..4b553b04c5 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RewardDestinationValidationsModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RewardDestinationValidationsModule.kt @@ -19,7 +19,7 @@ class RewardDestinationValidationsModule { fun provideFeeValidation() = RewardDestinationFeeValidation( feeExtractor = { it.fee }, availableBalanceProducer = { it.availableControllerBalance }, - errorProducer = { _, _ -> RewardDestinationValidationFailure.CannotPayFees } + errorProducer = { RewardDestinationValidationFailure.CannotPayFees } ) @Provides diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/SetControllerValidationsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/SetControllerValidationsModule.kt index f3bc28aecf..cab15bab83 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/SetControllerValidationsModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/SetControllerValidationsModule.kt @@ -23,7 +23,7 @@ class SetControllerValidationsModule { return EnoughAmountToTransferValidation( feeExtractor = { it.fee }, availableBalanceProducer = { it.transferable }, - errorProducer = { _, _ -> SetControllerValidationFailure.NOT_ENOUGH_TO_PAY_FEES } + errorProducer = { SetControllerValidationFailure.NOT_ENOUGH_TO_PAY_FEES } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/UnbondValidationsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/UnbondValidationsModule.kt index 7f0a39a38f..34414fe503 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/UnbondValidationsModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/UnbondValidationsModule.kt @@ -26,7 +26,7 @@ class UnbondValidationsModule { fun provideFeeValidation() = UnbondFeeValidation( feeExtractor = { it.fee }, availableBalanceProducer = { it.asset.transferable }, - errorProducer = { _, _ -> UnbondValidationFailure.CannotPayFees } + errorProducer = { UnbondValidationFailure.CannotPayFees } ) @FeatureScope diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/validations/RebagValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/validations/RebagValidationPayload.kt index 05662d7117..6dbd99c370 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/validations/RebagValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/validations/RebagValidationPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.bagList.rebag.validations import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class RebagValidationPayload( - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/validations/RebagValidationSystem.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/validations/RebagValidationSystem.kt index fd5fac2aae..d871e1a84d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/validations/RebagValidationSystem.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/bagList/rebag/validations/RebagValidationSystem.kt @@ -9,11 +9,11 @@ fun ValidationSystem.Companion.rebagValidationSystem(): RebagValidationSystem = sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> RebagValidationFailure.NotEnoughToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/validation/ProfitableActionValidation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/validation/ProfitableActionValidation.kt index 5f746dedcf..b15fe9f596 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/validation/ProfitableActionValidation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/validation/ProfitableActionValidation.kt @@ -4,16 +4,19 @@ import io.novafoundation.nova.common.validation.Validation import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.ValidationSystemBuilder import io.novafoundation.nova.common.validation.isTrueOrWarning +import io.novafoundation.nova.feature_wallet_api.domain.validation.FeeProducer +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeDecimalAmount import java.math.BigDecimal class ProfitableActionValidation( val amount: P.() -> BigDecimal, - val fee: P.() -> BigDecimal, + val fee: FeeProducer

, val error: (P) -> E ) : Validation { override suspend fun validate(value: P): ValidationStatus { - val isProfitable = value.fee() < value.amount() + // No matter who paid the fee we check that it is profitable + val isProfitable = fee(value).networkFeeDecimalAmount < value.amount() return isProfitable isTrueOrWarning { error(value) @@ -23,7 +26,7 @@ class ProfitableActionValidation( fun ValidationSystemBuilder.profitableAction( amount: P.() -> BigDecimal, - fee: P.() -> BigDecimal, + fee: FeeProducer

, error: (P) -> E ) { validate(ProfitableActionValidation(amount, fee, error)) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/bondMore/validations/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/bondMore/validations/Declarations.kt index 8039594413..3002b7b804 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/bondMore/validations/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/bondMore/validations/Declarations.kt @@ -37,7 +37,7 @@ private fun NominationPoolsBondMoreValidationSystemBuilder.poolIsNotDestroying(f private fun NominationPoolsBondMoreValidationSystemBuilder.enoughAvailableToStakeInPool(factory: PoolAvailableBalanceValidationFactory) { factory.enoughAvailableBalanceToStake( asset = { it.asset }, - fee = { it.fee.fee.amount }, + fee = { it.fee }, amount = { it.amount }, error = NominationPoolsBondMoreValidationFailure::NotEnoughToBond ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/Declarations.kt index abfd1ab8f9..d38c4dd878 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/Declarations.kt @@ -41,11 +41,11 @@ private fun NominationPoolsClaimRewardsValidationSystemBuilder.enoughToPayFees() sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> NominationPoolsClaimRewardsValidationFailure.NotEnoughBalanceToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) @@ -54,7 +54,7 @@ private fun NominationPoolsClaimRewardsValidationSystemBuilder.enoughToPayFees() private fun NominationPoolsClaimRewardsValidationSystemBuilder.profitableClaim() { profitableAction( amount = { pendingRewards }, - fee = { fee }, + fee = { it.fee }, error = { NominationPoolsClaimRewardsValidationFailure.NonProfitableClaim } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/NominationPoolsClaimRewardsValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/NominationPoolsClaimRewardsValidationPayload.kt index ad2271e1e0..123515249c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/NominationPoolsClaimRewardsValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/claimRewards/validations/NominationPoolsClaimRewardsValidationPayload.kt @@ -3,11 +3,12 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.claim import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigDecimal class NominationPoolsClaimRewardsValidationPayload( - val fee: BigDecimal, + val fee: DecimalFee, val pendingRewardsPlanks: Balance, val asset: Asset, val chain: Chain diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/validations/PoolAvailableBalanceValidation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/validations/PoolAvailableBalanceValidation.kt index 7f6ff08315..a6d159f54d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/validations/PoolAvailableBalanceValidation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/validations/PoolAvailableBalanceValidation.kt @@ -3,18 +3,21 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.commo import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer import io.novafoundation.nova.common.mixin.api.CustomDialogDisplayer.Payload.DialogAction import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.common.validation.TransformedFailure import io.novafoundation.nova.common.validation.Validation import io.novafoundation.nova.common.validation.ValidationFlowActions import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.ValidationSystemBuilder import io.novafoundation.nova.common.validation.isTrueOrWarning +import io.novafoundation.nova.feature_account_api.data.model.amountByRequestedAccount import io.novafoundation.nova.feature_staking_impl.R import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.NominationPoolsAvailableBalanceResolver import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount +import io.novafoundation.nova.feature_wallet_api.domain.validation.FeeProducer import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatPlanks import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -28,7 +31,7 @@ class PoolAvailableBalanceValidationFactory( context(ValidationSystemBuilder) fun enoughAvailableBalanceToStake( asset: (P) -> Asset, - fee: (P) -> Balance, + fee: FeeProducer

, amount: (P) -> BigDecimal, error: (PoolAvailableBalanceValidation.ValidationError.Context) -> E ) { @@ -47,7 +50,7 @@ class PoolAvailableBalanceValidationFactory( class PoolAvailableBalanceValidation( private val poolsAvailableBalanceResolver: NominationPoolsAvailableBalanceResolver, private val asset: (P) -> Asset, - private val fee: (P) -> Balance, + private val fee: FeeProducer

, private val amount: (P) -> BigDecimal, private val error: (ValidationError.Context) -> E ) : Validation { @@ -69,7 +72,7 @@ class PoolAvailableBalanceValidation( val asset = asset(value) val chainAsset = asset.token.configuration - val fee = fee(value) + val fee = fee(value)?.networkFee?.amountByRequestedAccount.orZero() val availableBalance = poolsAvailableBalanceResolver.availableBalanceToStartStaking(asset) val maxToStake = poolsAvailableBalanceResolver.maximumBalanceToStake(asset, fee) val enteredAmount = chainAsset.planksFromAmount(amount(value)) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/Declarations.kt index c231994800..122a82aa81 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/Declarations.kt @@ -23,11 +23,11 @@ private fun NominationPoolsRedeemValidationSystemBuilder.enoughToPayFees() { sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> NominationPoolsRedeemValidationFailure.NotEnoughBalanceToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/NominationPoolsRedeemValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/NominationPoolsRedeemValidationPayload.kt index b6be071f90..9069b16696 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/NominationPoolsRedeemValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/redeem/validations/NominationPoolsRedeemValidationPayload.kt @@ -1,11 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.redeem.validations import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import java.math.BigDecimal class NominationPoolsRedeemValidationPayload( - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset, val chain: Chain ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/Declarations.kt index a35dc438b5..2da505dcf3 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/Declarations.kt @@ -40,11 +40,11 @@ private fun NominationPoolsUnbondValidationSystemBuilder.enoughToPayFees() { sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, leftForFees -> + error = { context -> NominationPoolsUnbondValidationFailure.NotEnoughBalanceToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = leftForFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) @@ -54,7 +54,7 @@ private fun NominationPoolsUnbondValidationSystemBuilder.enoughToUnbond() { sufficientBalance( available = { it.stakedBalance }, amount = { it.amount }, - error = { _, _ -> NominationPoolsUnbondValidationFailure.NotEnoughToUnbond } + error = { NominationPoolsUnbondValidationFailure.NotEnoughToUnbond } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/NominationPoolsUnbondValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/NominationPoolsUnbondValidationPayload.kt index 7bbbedb37e..dbdeadec6b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/NominationPoolsUnbondValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/unbond/validations/NominationPoolsUnbondValidationPayload.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_staking_impl.domain.nominationPools.unbon import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.models.PoolMember import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import kotlinx.coroutines.CoroutineScope import java.math.BigDecimal @@ -10,7 +11,7 @@ data class NominationPoolsUnbondValidationPayload( val poolMember: PoolMember, val stakedBalance: BigDecimal, val amount: BigDecimal, - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset, val chain: Chain, val sharedComputationScope: CoroutineScope, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/validations/ParachainStakingRebondValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/validations/ParachainStakingRebondValidationPayload.kt index 554d02b407..b4f9c8857b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/validations/ParachainStakingRebondValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/validations/ParachainStakingRebondValidationPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.rebond.validations import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class ParachainStakingRebondValidationPayload( val asset: Asset, - val fee: BigDecimal, + val fee: DecimalFee, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/validations/ParachainStakingUnbondValidationSystem.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/validations/ParachainStakingUnbondValidationSystem.kt index 7c927aa664..1450f8e8bf 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/validations/ParachainStakingUnbondValidationSystem.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/rebond/validations/ParachainStakingUnbondValidationSystem.kt @@ -9,6 +9,6 @@ fun ValidationSystem.Companion.parachainStakingRebond(): ParachainStakingRebondV sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { _, _ -> ParachainStakingRebondValidationFailure.NotEnoughBalanceToPayFees } + error = { ParachainStakingRebondValidationFailure.NotEnoughBalanceToPayFees } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/validations/ParachainStakingRedeemValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/validations/ParachainStakingRedeemValidationPayload.kt index 10f66b9b8f..a6fa8e5b20 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/validations/ParachainStakingRedeemValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/validations/ParachainStakingRedeemValidationPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.redeem.validations import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class ParachainStakingRedeemValidationPayload( val asset: Asset, - val fee: BigDecimal, + val fee: DecimalFee, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/validations/ParachainStakingUnbondValidationSystem.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/validations/ParachainStakingUnbondValidationSystem.kt index 156f2ff257..4add7a9455 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/validations/ParachainStakingUnbondValidationSystem.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/redeem/validations/ParachainStakingUnbondValidationSystem.kt @@ -9,6 +9,6 @@ fun ValidationSystem.Companion.parachainStakingRedeem(): ParachainStakingRedeemV sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { _, _ -> ParachainStakingRedeemValidationFailure.NotEnoughBalanceToPayFees } + error = { ParachainStakingRedeemValidationFailure.NotEnoughBalanceToPayFees } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/validations/StartParachainStakingValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/validations/StartParachainStakingValidationPayload.kt index 0f8303f0c7..e1f7b3fa8d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/validations/StartParachainStakingValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/validations/StartParachainStakingValidationPayload.kt @@ -3,11 +3,12 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.star import io.novafoundation.nova.feature_staking_api.domain.model.parachain.DelegatorState import io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.common.model.Collator import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal class StartParachainStakingValidationPayload( val amount: BigDecimal, - val fee: BigDecimal, + val fee: DecimalFee, val collator: Collator, val asset: Asset, val delegatorState: DelegatorState, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/validations/StartParachainStakingValidationSystem.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/validations/StartParachainStakingValidationSystem.kt index ec46c5e34e..3764a532e2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/validations/StartParachainStakingValidationSystem.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/start/validations/StartParachainStakingValidationSystem.kt @@ -40,7 +40,7 @@ private fun StartParachainStakingValidationSystemBuilder.enoughToPayFees() { sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { _, _ -> StartParachainStakingValidationFailure.NotEnoughBalanceToPayFees } + error = { StartParachainStakingValidationFailure.NotEnoughBalanceToPayFees } ) } @@ -49,7 +49,7 @@ private fun StartParachainStakingValidationSystemBuilder.enoughStakeable() { fee = { it.fee }, available = { it.stakeableAmount() }, amount = { it.amount }, - error = { _, _ -> StartParachainStakingValidationFailure.NotEnoughBalanceToPayFees } + error = { StartParachainStakingValidationFailure.NotEnoughBalanceToPayFees } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/validations/flow/ParachainStakingUnbondValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/validations/flow/ParachainStakingUnbondValidationPayload.kt index 8583bbc2a3..a12ede1cfb 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/validations/flow/ParachainStakingUnbondValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/validations/flow/ParachainStakingUnbondValidationPayload.kt @@ -2,11 +2,12 @@ package io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.unbo import io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.common.model.Collator import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal data class ParachainStakingUnbondValidationPayload( val amount: BigDecimal, - val fee: BigDecimal, + val fee: DecimalFee, val collator: Collator, val asset: Asset, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/validations/flow/ParachainStakingUnbondValidationSystem.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/validations/flow/ParachainStakingUnbondValidationSystem.kt index 3ed5fb041e..be3ca659ad 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/validations/flow/ParachainStakingUnbondValidationSystem.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/unbond/validations/flow/ParachainStakingUnbondValidationSystem.kt @@ -28,6 +28,6 @@ fun ValidationSystem.Companion.parachainStakingUnbond( sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { _, _ -> ParachainStakingUnbondValidationFailure.NotEnoughBalanceToPayFees } + error = { ParachainStakingUnbondValidationFailure.NotEnoughBalanceToPayFees } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/FirstTaskCanExecute.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/FirstTaskCanExecute.kt index 2c3269a157..13306d0f5f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/FirstTaskCanExecute.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/FirstTaskCanExecute.kt @@ -20,7 +20,7 @@ class FirstTaskCanExecute( val token = value.asset.token val balanceBeforeTransaction = value.asset.transferable - val balanceAfterTransaction = balanceBeforeTransaction - value.fee + val balanceAfterTransaction = balanceBeforeTransaction - value.fee.networkFeeDecimalAmount val chainId = value.asset.token.configuration.chainId @@ -32,7 +32,7 @@ class FirstTaskCanExecute( return when { taskExecutionFee > balanceAfterTransaction -> YieldBoostValidationFailure.FirstTaskCannotExecute( minimumBalanceRequired = taskExecutionFee, - networkFee = value.fee, + networkFee = value.fee.networkFeeDecimalAmount, availableBalanceBeforeFees = balanceBeforeTransaction, type = EXECUTION_FEE, chainAsset = token.configuration @@ -40,7 +40,7 @@ class FirstTaskCanExecute( threshold > balanceAfterTransaction -> YieldBoostValidationFailure.FirstTaskCannotExecute( minimumBalanceRequired = threshold, - networkFee = value.fee, + networkFee = value.fee.networkFeeDecimalAmount, availableBalanceBeforeFees = balanceBeforeTransaction, type = THRESHOLD, chainAsset = token.configuration diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/ValidationSystem.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/ValidationSystem.kt index 7dc8d65553..19c494f6de 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/ValidationSystem.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/ValidationSystem.kt @@ -16,11 +16,11 @@ fun ValidationSystem.Companion.yieldBoost( sufficientBalance( fee = { it.fee }, available = { it.asset.transferable }, - error = { payload, availableToPayFees -> + error = { context -> YieldBoostValidationFailure.NotEnoughToPayToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = availableToPayFees, - fee = payload.fee + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/YieldBoostValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/YieldBoostValidationPayload.kt index 9e6a3b6785..6211cabd9c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/YieldBoostValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/parachainStaking/yieldBoost/validations/YieldBoostValidationPayload.kt @@ -4,12 +4,12 @@ import io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.commo import io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.yieldBoost.YieldBoostConfiguration import io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.yieldBoost.YieldBoostTask import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class YieldBoostValidationPayload( val collator: Collator, val activeTasks: List, val configuration: YieldBoostConfiguration, val asset: Asset, - val fee: BigDecimal, + val fee: DecimalFee, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/validations/AvailableBalanceGapValidation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/validations/AvailableBalanceGapValidation.kt index 0364d22c13..09ad392904 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/validations/AvailableBalanceGapValidation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/validations/AvailableBalanceGapValidation.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.start.common. import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.valid import io.novafoundation.nova.common.validation.validationError +import io.novafoundation.nova.feature_account_api.data.model.amountByRequestedAccount import io.novafoundation.nova.feature_staking_impl.data.asset import io.novafoundation.nova.feature_staking_impl.data.chain import io.novafoundation.nova.feature_staking_impl.data.stakingType @@ -18,7 +19,7 @@ class AvailableBalanceGapValidation( override suspend fun validate(value: StartMultiStakingValidationPayload): ValidationStatus { val amount = value.selection.stake val stakingOption = value.selection.stakingOption - val fee = value.fee.fee.amount + val fee = value.fee.networkFee.amountByRequestedAccount val maxToStakeWithMinStakes = candidates.map { val maximumToStake = it.maximumToStake(value.asset, fee) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt index 0ec0861cd7..8f236cbe44 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingProperties.kt @@ -111,14 +111,14 @@ private class DirectStakingProperties( private fun StartMultiStakingValidationSystemBuilder.enoughAvailableToStake() { sufficientBalance( - fee = { it.fee.networkFeeDecimalAmount }, + fee = { it.fee }, available = { it.amountOf(availableBalance(it.asset)) }, amount = { it.amountOf(it.selection.stake) }, - error = { payload, availableToPayFees -> + error = { context -> StartMultiStakingValidationFailure.NotEnoughToPayFees( - chainAsset = payload.asset.token.configuration, - maxUsable = availableToPayFees, - fee = payload.fee.networkFeeDecimalAmount + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.availableToPayFees, + fee = context.fee ) } ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/pools/NominationPoolStakingProperties.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/pools/NominationPoolStakingProperties.kt index a934dc9c8c..e0a5743a11 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/pools/NominationPoolStakingProperties.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/pools/NominationPoolStakingProperties.kt @@ -82,7 +82,7 @@ private class NominationPoolStakingProperties( poolAvailableBalanceValidationFactory.enoughAvailableBalanceToStake( asset = { it.asset }, - fee = { it.fee.fee.amount }, + fee = { it.fee }, amount = { it.selection.stakeAmount() }, error = StartMultiStakingValidationFailure::PoolAvailableBalance ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/selectionType/ManualMultiStakingSelectionType.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/selectionType/ManualMultiStakingSelectionType.kt index a5887a5956..f59e5e242c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/selectionType/ManualMultiStakingSelectionType.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/selectionType/ManualMultiStakingSelectionType.kt @@ -38,8 +38,8 @@ class ManualMultiStakingSelectionType( sufficientBalance( available = { it.amountOf(availableBalance(it.asset)) }, amount = { it.amountOf(it.selection.stake) }, - error = { _, _ -> StartMultiStakingValidationFailure.NotEnoughAvailableToStake }, - fee = { it.fee.decimalAmount } + error = { StartMultiStakingValidationFailure.NotEnoughAvailableToStake }, + fee = { it.fee } ) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/bond/BondMoreValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/bond/BondMoreValidationPayload.kt index 670312999d..3dd0939360 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/bond/BondMoreValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/bond/BondMoreValidationPayload.kt @@ -1,11 +1,12 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.bond import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal class BondMoreValidationPayload( val stashAddress: String, - val fee: BigDecimal, + val fee: DecimalFee, val amount: BigDecimal, val stashAsset: Asset, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/bond/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/bond/Declarations.kt index 7f18f255b9..d854dafe11 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/bond/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/bond/Declarations.kt @@ -21,7 +21,7 @@ private fun BondMoreValidationSystemBuilder.enoughToPayFees() { sufficientBalance( fee = { it.fee }, available = { it.stashAsset.transferable }, - error = { _, _ -> BondMoreValidationFailure.NOT_ENOUGH_TO_PAY_FEES } + error = { BondMoreValidationFailure.NOT_ENOUGH_TO_PAY_FEES } ) } @@ -30,7 +30,7 @@ private fun BondMoreValidationSystemBuilder.enoughStakeable() { fee = { it.fee }, available = { it.stashAsset.stakeable }, amount = { it.amount }, - error = { _, _ -> BondMoreValidationFailure.NOT_ENOUGH_STAKEABLE } + error = { BondMoreValidationFailure.NOT_ENOUGH_STAKEABLE } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/SetControllerValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/SetControllerValidationPayload.kt index a2376a7d21..4d35cf660c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/SetControllerValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/SetControllerValidationPayload.kt @@ -1,10 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.controller +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal class SetControllerValidationPayload( val stashAddress: String, val controllerAddress: String, - val fee: BigDecimal, + val fee: DecimalFee, val transferable: BigDecimal ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/payout/MakePayoutPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/payout/MakePayoutPayload.kt index af5d79dab0..d08cf3ca22 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/payout/MakePayoutPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/payout/MakePayoutPayload.kt @@ -2,11 +2,12 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.payout import io.novafoundation.nova.feature_staking_impl.data.model.Payout import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal class MakePayoutPayload( val originAddress: String, - val fee: BigDecimal, + val fee: DecimalFee, val totalReward: BigDecimal, val asset: Asset, val payouts: List diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/rebond/RebondValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/rebond/RebondValidationPayload.kt index e412658f94..aacb003041 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/rebond/RebondValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/rebond/RebondValidationPayload.kt @@ -1,10 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.rebond import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal class RebondValidationPayload( val controllerAsset: Asset, - val fee: BigDecimal, + val fee: DecimalFee, val rebondAmount: BigDecimal ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/reedeem/RedeemValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/reedeem/RedeemValidationPayload.kt index f3550b71be..f8cf40c06f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/reedeem/RedeemValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/reedeem/RedeemValidationPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.reedeem import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class RedeemValidationPayload( - val fee: BigDecimal, + val fee: DecimalFee, val asset: Asset ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/rewardDestination/RewardDestinationValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/rewardDestination/RewardDestinationValidationPayload.kt index d79e06cfaf..afa7a67d6e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/rewardDestination/RewardDestinationValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/rewardDestination/RewardDestinationValidationPayload.kt @@ -1,10 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.rewardDestination import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal class RewardDestinationValidationPayload( val availableControllerBalance: BigDecimal, - val fee: BigDecimal, + val fee: DecimalFee, val stashState: StakingState.Stash ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/setup/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/setup/Declarations.kt index 13f78ef77c..c50546c49e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/setup/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/setup/Declarations.kt @@ -29,6 +29,6 @@ private fun SetupStakingValidationSystemBuilder.enoughToPayFee() { sufficientBalance( fee = { it.maxFee }, available = { it.controllerAsset.transferable }, - error = { _, _ -> SetupStakingValidationFailure.CannotPayFee } + error = { SetupStakingValidationFailure.CannotPayFee } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/setup/SetupStakingPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/setup/SetupStakingPayload.kt index 42fb697ded..47837216d2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/setup/SetupStakingPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/setup/SetupStakingPayload.kt @@ -1,9 +1,9 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.setup import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import java.math.BigDecimal +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee class SetupStakingPayload( - val maxFee: BigDecimal, + val maxFee: DecimalFee, val controllerAsset: Asset, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/unbond/UnbondValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/unbond/UnbondValidationPayload.kt index 17ecb18f34..a2b4b3eec6 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/unbond/UnbondValidationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/unbond/UnbondValidationPayload.kt @@ -2,11 +2,12 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.unbond import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal data class UnbondValidationPayload( val stash: StakingState.Stash, - val fee: BigDecimal, + val fee: DecimalFee, val amount: BigDecimal, val asset: Asset, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/bagList/rebag/RebagViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/bagList/rebag/RebagViewModel.kt index 889607d1db..9008950530 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/bagList/rebag/RebagViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/bagList/rebag/RebagViewModel.kt @@ -25,6 +25,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.bagList.rebag.mo import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatPlanksRange import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.WithFeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.state.chain @@ -111,8 +112,12 @@ class RebagViewModel( private fun rebagIfValid() { launch { - val fee = originFeeMixin.awaitFee() - val validationPayload = RebagValidationPayload(fee, stashAsset.first()) + _showNextProgress.value = true + + val validationPayload = RebagValidationPayload( + fee = originFeeMixin.awaitDecimalFee(), + asset = stashAsset.first() + ) validationExecutor.requireValid( validationSystem = validationSystem, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/bondMore/confirm/NominationPoolsConfirmBondMoreViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/bondMore/confirm/NominationPoolsConfirmBondMoreViewModel.kt index 0858c217cf..e87d40133b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/bondMore/confirm/NominationPoolsConfirmBondMoreViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/bondMore/confirm/NominationPoolsConfirmBondMoreViewModel.kt @@ -71,7 +71,7 @@ class NominationPoolsConfirmBondMoreViewModel( .shareInBackground() val feeStatusFlow = assetFlow.map { asset -> - val feeModel = mapFeeToFeeModel(decimalFee.fee, asset.token) + val feeModel = mapFeeToFeeModel(decimalFee.genericFee, asset.token) FeeStatus.Loaded(feeModel) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/claimRewards/NominationPoolsClaimRewardsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/claimRewards/NominationPoolsClaimRewardsViewModel.kt index 8e8c0e1c3a..9c9b329d13 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/claimRewards/NominationPoolsClaimRewardsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/claimRewards/NominationPoolsClaimRewardsViewModel.kt @@ -19,6 +19,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.claimR import io.novafoundation.nova.feature_staking_impl.presentation.NominationPoolsRouter import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel @@ -95,10 +96,12 @@ class NominationPoolsClaimRewardsViewModel( } private fun claimRewardsIfValid() = launch { + _showNextProgress.value = true + val shouldRestake = shouldRestakeInput.first() val payload = NominationPoolsClaimRewardsValidationPayload( - fee = feeLoaderMixin.awaitFee(), + fee = feeLoaderMixin.awaitDecimalFee(), pendingRewardsPlanks = pendingRewards.first(), asset = assetFlow.first(), chain = stakingSharedState.chain() diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/redeem/NominationPoolsRedeemViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/redeem/NominationPoolsRedeemViewModel.kt index 544de66a8a..94ee117ac1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/redeem/NominationPoolsRedeemViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/redeem/NominationPoolsRedeemViewModel.kt @@ -20,6 +20,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.redeem import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel @@ -102,11 +103,11 @@ class NominationPoolsRedeemViewModel( } private fun redeemIfValid() = launch { - val asset = assetFlow.first() + _showNextProgress.value = true val payload = NominationPoolsRedeemValidationPayload( - fee = feeLoaderMixin.awaitFee(), - asset = asset, + fee = feeLoaderMixin.awaitDecimalFee(), + asset = assetFlow.first(), chain = stakingSharedState.chain() ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/confirm/NominationPoolsConfirmUnbondPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/confirm/NominationPoolsConfirmUnbondPayload.kt index e039902a87..6907a65680 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/confirm/NominationPoolsConfirmUnbondPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/confirm/NominationPoolsConfirmUnbondPayload.kt @@ -1,11 +1,12 @@ package io.novafoundation.nova.feature_staking_impl.presentation.nominationPools.unbond.confirm import android.os.Parcelable +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize import java.math.BigDecimal @Parcelize class NominationPoolsConfirmUnbondPayload( val amount: BigDecimal, - val fee: BigDecimal, + val fee: FeeParcelModel, ) : Parcelable diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/confirm/NominationPoolsConfirmUnbondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/confirm/NominationPoolsConfirmUnbondViewModel.kt index cd425281ec..d54df9df77 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/confirm/NominationPoolsConfirmUnbondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/confirm/NominationPoolsConfirmUnbondViewModel.kt @@ -24,6 +24,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeStatus +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.Flow @@ -49,6 +50,8 @@ class NominationPoolsConfirmUnbondViewModel( ExternalActions by externalActions, Validatable by validationExecutor { + private val decimalFee = mapFeeFromParcel(payload.fee) + private val _showNextProgress = MutableStateFlow(false) val showNextProgress: Flow = _showNextProgress @@ -66,7 +69,7 @@ class NominationPoolsConfirmUnbondViewModel( .shareInBackground() val feeStatusFlow = assetFlow.map { asset -> - val feeModel = mapFeeToFeeModel(payload.fee, asset.token) + val feeModel = mapFeeToFeeModel(decimalFee.genericFee, asset.token) FeeStatus.Loaded(feeModel) } @@ -102,7 +105,7 @@ class NominationPoolsConfirmUnbondViewModel( val stakedBalance = asset.token.amountFromPlanks(stakedBalance.first()) val payload = NominationPoolsUnbondValidationPayload( - fee = payload.fee, + fee = decimalFee, amount = payload.amount, poolMember = poolMemberFlow.first(), asset = asset, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/setup/NominationPoolsSetupUnbondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/setup/NominationPoolsSetupUnbondViewModel.kt index f0269876fe..9eafdda2f2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/setup/NominationPoolsSetupUnbondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/nominationPools/unbond/setup/NominationPoolsSetupUnbondViewModel.kt @@ -21,8 +21,10 @@ import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.create +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.MutableStateFlow @@ -110,7 +112,7 @@ class NominationPoolsSetupUnbondViewModel( val stakedBalance = asset.token.amountFromPlanks(stakedBalance.first()) val payload = NominationPoolsUnbondValidationPayload( - fee = originFeeMixin.awaitFee(), + fee = originFeeMixin.awaitDecimalFee(), poolMember = poolMemberFlow.first(), stakedBalance = stakedBalance, asset = asset, @@ -134,7 +136,7 @@ class NominationPoolsSetupUnbondViewModel( private fun openConfirm(validationPayload: NominationPoolsUnbondValidationPayload) = launch { val confirmPayload = NominationPoolsConfirmUnbondPayload( amount = validationPayload.amount, - fee = validationPayload.fee + fee = mapFeeToParcel(validationPayload.fee) ) router.openConfirmUnbond(confirmPayload) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/rebond/ParachainStakingRebondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/rebond/ParachainStakingRebondViewModel.kt index 44fc12b2e8..d11875b3bf 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/rebond/ParachainStakingRebondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/rebond/ParachainStakingRebondViewModel.kt @@ -25,6 +25,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -36,7 +37,6 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.math.BigDecimal class ParachainStakingRebondViewModel( private val router: ParachainStakingRouter, @@ -125,21 +125,21 @@ class ParachainStakingRebondViewModel( router.openCollatorDetails(StakeTargetDetailsPayload.parachain(parcel, collatorsUseCase)) } - private fun sendTransactionIfValid() = requireFee { fee -> - launch { - val payload = ParachainStakingRebondValidationPayload( - fee = fee, - asset = assetFlow.first() - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { parachainStakingRebondValidationFailure(it, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer() - ) { - sendTransaction() - } + private fun sendTransactionIfValid() = launch { + _showNextProgress.value = true + + val payload = ParachainStakingRebondValidationPayload( + fee = feeLoaderMixin.awaitDecimalFee(), + asset = assetFlow.first() + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { parachainStakingRebondValidationFailure(it, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer() + ) { + sendTransaction() } } @@ -154,9 +154,4 @@ class ParachainStakingRebondViewModel( _showNextProgress.value = false } - - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/redeem/ParachainStakingRedeemViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/redeem/ParachainStakingRedeemViewModel.kt index 717a18de79..c816c8c289 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/redeem/ParachainStakingRedeemViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/redeem/ParachainStakingRedeemViewModel.kt @@ -20,6 +20,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.redee import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState @@ -30,7 +31,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import java.math.BigDecimal class ParachainStakingRedeemViewModel( private val router: StakingRouter, @@ -105,21 +105,21 @@ class ParachainStakingRedeemViewModel( externalActions.showExternalActions(ExternalActions.Type.Address(address), selectedAssetState.chain()) } - private fun sendTransactionIfValid() = requireFee { fee -> - launch { - val payload = ParachainStakingRedeemValidationPayload( - fee = fee, - asset = assetFlow.first() - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { parachainStakingRedeemValidationFailure(it, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer() - ) { - sendTransaction() - } + private fun sendTransactionIfValid() = launch { + _showNextProgress.value = true + + val payload = ParachainStakingRedeemValidationPayload( + fee = feeLoaderMixin.awaitDecimalFee(), + asset = assetFlow.first() + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { parachainStakingRedeemValidationFailure(it, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer() + ) { + sendTransaction() } } @@ -134,9 +134,4 @@ class ParachainStakingRedeemViewModel( _showNextProgress.value = false } - - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/confirm/ConfirmStartParachainStakingViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/confirm/ConfirmStartParachainStakingViewModel.kt index 9a845b9243..ae57d4b17c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/confirm/ConfirmStartParachainStakingViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/confirm/ConfirmStartParachainStakingViewModel.kt @@ -39,6 +39,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.validators.detai import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -50,7 +51,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.math.BigDecimal class ConfirmStartParachainStakingViewModel( private val parachainStakingRouter: ParachainStakingRouter, @@ -77,6 +77,8 @@ class ConfirmStartParachainStakingViewModel( FeeLoaderMixin by feeLoaderMixin, ExternalActions by externalActions { + private val decimalFee = mapFeeFromParcel(payload.fee) + // Take state only once since subscribing to it might cause switch to Delegator state while waiting for tx confirmation private val delegatorStateFlow = flowOf { delegatorStateUseCase.currentDelegatorState() } .shareInBackground() @@ -149,27 +151,25 @@ class ConfirmStartParachainStakingViewModel( } private fun setInitialFee() = launch { - feeLoaderMixin.setFee(payload.fee) + feeLoaderMixin.setFee(decimalFee.genericFee) } - private fun sendTransactionIfValid() = requireFee { _ -> - launch { - val payload = StartParachainStakingValidationPayload( - amount = payload.amount, - fee = payload.fee, - collator = collator(), - asset = assetFlow.first(), - delegatorState = delegatorStateFlow.first(), - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { startParachainStakingValidationFailure(it, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer() - ) { - sendTransaction() - } + private fun sendTransactionIfValid() = launch { + val payload = StartParachainStakingValidationPayload( + amount = payload.amount, + fee = decimalFee, + collator = collator(), + asset = assetFlow.first(), + delegatorState = delegatorStateFlow.first(), + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { startParachainStakingValidationFailure(it, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer() + ) { + sendTransaction() } } @@ -203,9 +203,4 @@ class ConfirmStartParachainStakingViewModel( StartParachainStakingMode.BOND_MORE -> parachainStakingRouter.returnToStakingMain() } } - - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/confirm/model/ConfirmStartParachainStakingPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/confirm/model/ConfirmStartParachainStakingPayload.kt index 387645ca83..b2a44d5ba5 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/confirm/model/ConfirmStartParachainStakingPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/confirm/model/ConfirmStartParachainStakingPayload.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_staking_impl.presentation.parachainStakin import android.os.Parcelable import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.select.model.CollatorParcelModel import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.start.common.StartParachainStakingMode +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize import java.math.BigDecimal @@ -10,6 +11,6 @@ import java.math.BigDecimal class ConfirmStartParachainStakingPayload( val collator: CollatorParcelModel, val amount: BigDecimal, - val fee: BigDecimal, + val fee: FeeParcelModel, val flowMode: StartParachainStakingMode ) : Parcelable diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/setup/StartParachainStakingViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/setup/StartParachainStakingViewModel.kt index 324aadde55..f7fc9dc156 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/setup/StartParachainStakingViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/start/setup/StartParachainStakingViewModel.kt @@ -43,7 +43,10 @@ import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import jp.co.soramitsu.fearless_utils.extensions.fromHex import kotlinx.coroutines.Dispatchers @@ -263,34 +266,34 @@ class StartParachainStakingViewModel( .launchIn(this) } - private fun maybeGoToNext() = requireFee { fee -> - launch { - val collator = selectedCollatorFlow.first() ?: return@launch - val amount = amountChooserMixin.amount.first() + private fun maybeGoToNext() = launch { + validationInProgress.value = true - val payload = StartParachainStakingValidationPayload( - amount = amount, - fee = fee, - asset = assetFlow.first(), - collator = collator, - delegatorState = currentDelegatorStateFlow.first(), - ) + val collator = selectedCollatorFlow.first() ?: return@launch + val amount = amountChooserMixin.amount.first() - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { startParachainStakingValidationFailure(it, resourceManager) }, - progressConsumer = validationInProgress.progressConsumer() - ) { - validationInProgress.value = false + val payload = StartParachainStakingValidationPayload( + amount = amount, + fee = feeLoaderMixin.awaitDecimalFee(), + asset = assetFlow.first(), + collator = collator, + delegatorState = currentDelegatorStateFlow.first(), + ) - goToNextStep(fee = fee, amount = amount, collator = collator) - } + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { startParachainStakingValidationFailure(it, resourceManager) }, + progressConsumer = validationInProgress.progressConsumer() + ) { + validationInProgress.value = false + + goToNextStep(fee = it.fee, amount = amount, collator = collator) } } private fun goToNextStep( - fee: BigDecimal, + fee: DecimalFee, amount: BigDecimal, collator: Collator, ) = launch { @@ -298,16 +301,11 @@ class StartParachainStakingViewModel( ConfirmStartParachainStakingPayload( collator = mapCollatorToCollatorParcelModel(collator), amount = amount, - fee = fee, + fee = mapFeeToParcel(fee), flowMode = payload.flowMode ) } router.openConfirmStartStaking(payload) } - - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/confirm/ParachainStakingUnbondConfirmViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/confirm/ParachainStakingUnbondConfirmViewModel.kt index 59441612d8..16c2b0d4b1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/confirm/ParachainStakingUnbondConfirmViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/confirm/ParachainStakingUnbondConfirmViewModel.kt @@ -32,6 +32,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.validators.detai import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -66,6 +67,8 @@ class ParachainStakingUnbondConfirmViewModel( FeeLoaderMixin by feeLoaderMixin, ExternalActions by externalActions { + private val decimalFee = mapFeeFromParcel(payload.fee) + val hintsMixin = hintsMixinFactory.create(coroutineScope = this) private val assetFlow = assetUseCase.currentAssetFlow() @@ -125,13 +128,13 @@ class ParachainStakingUnbondConfirmViewModel( } private fun setInitialFee() = launch { - feeLoaderMixin.setFee(payload.fee) + feeLoaderMixin.setFee(decimalFee.genericFee) } private fun sendTransactionIfValid() = launch { val payload = ParachainStakingUnbondValidationPayload( amount = payload.amount, - fee = payload.fee, + fee = decimalFee, collator = collator(), asset = assetFlow.first() ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/confirm/model/ParachainStakingUnbondConfirmPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/confirm/model/ParachainStakingUnbondConfirmPayload.kt index 85092bcb65..e2a0805eee 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/confirm/model/ParachainStakingUnbondConfirmPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/confirm/model/ParachainStakingUnbondConfirmPayload.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_staking_impl.presentation.parachainStakin import android.os.Parcelable import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.select.model.CollatorParcelModel +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize import java.math.BigDecimal @@ -9,5 +10,5 @@ import java.math.BigDecimal class ParachainStakingUnbondConfirmPayload( val collator: CollatorParcelModel, val amount: BigDecimal, - val fee: BigDecimal + val fee: FeeParcelModel ) : Parcelable diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/setup/ParachainStakingUnbondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/setup/ParachainStakingUnbondViewModel.kt index 3ad56531d3..82e233197c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/setup/ParachainStakingUnbondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/unbond/setup/ParachainStakingUnbondViewModel.kt @@ -36,7 +36,10 @@ import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.feature_wallet_api.presentation.model.transferableAmountModel import jp.co.soramitsu.fearless_utils.extensions.fromHex @@ -206,31 +209,31 @@ class ParachainStakingUnbondViewModel( } } - private fun maybeGoToNext() = requireFee { fee -> - launch { - val payload = ParachainStakingUnbondValidationPayload( - amount = amountChooserMixin.amount.first(), - fee = fee, - asset = assetFlow.first(), - collator = selectedCollatorFlow.first() - ) + private fun maybeGoToNext() = launch { + validationInProgress.value = true - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { parachainStakingUnbondValidationFailure(it, resourceManager) }, - autoFixPayload = ::parachainStakingUnbondPayloadAutoFix, - progressConsumer = validationInProgress.progressConsumer() - ) { fixedPayload -> - validationInProgress.value = false + val payload = ParachainStakingUnbondValidationPayload( + amount = amountChooserMixin.amount.first(), + fee = feeLoaderMixin.awaitDecimalFee(), + asset = assetFlow.first(), + collator = selectedCollatorFlow.first() + ) - goToNextStep(fee = fee, amount = fixedPayload.amount, collator = fixedPayload.collator) - } + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { parachainStakingUnbondValidationFailure(it, resourceManager) }, + autoFixPayload = ::parachainStakingUnbondPayloadAutoFix, + progressConsumer = validationInProgress.progressConsumer() + ) { fixedPayload -> + validationInProgress.value = false + + goToNextStep(fee = fixedPayload.fee, amount = fixedPayload.amount, collator = fixedPayload.collator) } } private fun goToNextStep( - fee: BigDecimal, + fee: DecimalFee, amount: BigDecimal, collator: Collator, ) = launch { @@ -238,15 +241,10 @@ class ParachainStakingUnbondViewModel( ParachainStakingUnbondConfirmPayload( collator = mapCollatorToCollatorParcelModel(collator), amount = amount, - fee = fee + fee = mapFeeToParcel(fee) ) } router.openConfirmUnbond(payload) } - - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/confirm/YieldBoostConfirmViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/confirm/YieldBoostConfirmViewModel.kt index 19e7830121..f274da32b0 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/confirm/YieldBoostConfirmViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/confirm/YieldBoostConfirmViewModel.kt @@ -36,6 +36,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.yieldBoost.confirm.model.YieldBoostConfirmPayload import io.novafoundation.nova.feature_wallet_api.domain.AssetUseCase import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -69,6 +70,8 @@ class YieldBoostConfirmViewModel( FeeLoaderMixin by feeLoaderMixin, ExternalActions by externalActions { + private val decimalFee = mapFeeFromParcel(payload.fee) + private val assetFlow = assetUseCase.currentAssetFlow() .shareInBackground() @@ -138,14 +141,14 @@ class YieldBoostConfirmViewModel( } private fun setInitialFee() = launch { - feeLoaderMixin.setFee(payload.fee) + feeLoaderMixin.setFee(decimalFee.genericFee) } private fun sendTransactionIfValid() = launch { val payload = YieldBoostValidationPayload( collator = collator(), configuration = yieldBoostConfiguration(), - fee = payload.fee, + fee = decimalFee, activeTasks = activeTasksFlow.first(), asset = assetFlow.first() ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/confirm/model/YieldBoostConfirmPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/confirm/model/YieldBoostConfirmPayload.kt index 825efcc681..98b776a517 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/confirm/model/YieldBoostConfirmPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/confirm/model/YieldBoostConfirmPayload.kt @@ -3,15 +3,15 @@ package io.novafoundation.nova.feature_staking_impl.presentation.parachainStakin import android.os.Parcelable import io.novafoundation.nova.feature_staking_impl.domain.parachainStaking.yieldBoost.YieldBoostConfiguration import io.novafoundation.nova.feature_staking_impl.presentation.parachainStaking.collator.select.model.CollatorParcelModel +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize -import java.math.BigDecimal import java.math.BigInteger @Parcelize class YieldBoostConfirmPayload( val collator: CollatorParcelModel, val configurationParcel: YieldBoostConfigurationParcel, - val fee: BigDecimal, + val fee: FeeParcelModel, ) : Parcelable sealed class YieldBoostConfigurationParcel(open val collatorIdHex: String) : Parcelable { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/setup/SetupYieldBoostViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/setup/SetupYieldBoostViewModel.kt index 00b82be1f4..ace1a5bf26 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/setup/SetupYieldBoostViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/parachainStaking/yieldBoost/setup/SetupYieldBoostViewModel.kt @@ -53,7 +53,10 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.setAmountInput import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.connectWith +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import jp.co.soramitsu.fearless_utils.extensions.toHexString import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview @@ -330,37 +333,37 @@ class SetupYieldBoostViewModel( selectedCollatorFlow.emit(mostRelevantCollator.collator) } - private fun maybeGoToNext() = requireFee { fee -> - launch { - val payload = YieldBoostValidationPayload( - collator = selectedCollatorFlow.first(), - configuration = modifiedYieldBoostConfiguration.first(), - fee = fee, - activeTasks = activeTasksFlow.first(), - asset = assetFlow.first() - ) + private fun maybeGoToNext() = launch { + validationInProgressFlow.value = true - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { yieldBoostValidationFailure(it, resourceManager) }, - progressConsumer = validationInProgressFlow.progressConsumer() - ) { - validationInProgressFlow.value = false + val payload = YieldBoostValidationPayload( + collator = selectedCollatorFlow.first(), + configuration = modifiedYieldBoostConfiguration.first(), + fee = feeLoaderMixin.awaitDecimalFee(), + activeTasks = activeTasksFlow.first(), + asset = assetFlow.first() + ) - goToNextStep(fee = it.fee, collator = it.collator, configuration = it.configuration) - } + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { yieldBoostValidationFailure(it, resourceManager) }, + progressConsumer = validationInProgressFlow.progressConsumer() + ) { + validationInProgressFlow.value = false + + goToNextStep(fee = it.fee, collator = it.collator, configuration = it.configuration) } } private fun goToNextStep( - fee: BigDecimal, + fee: DecimalFee, configuration: YieldBoostConfiguration, collator: Collator, ) = launch { val payload = withContext(Dispatchers.Default) { YieldBoostConfirmPayload( - fee = fee, + fee = mapFeeToParcel(fee), configurationParcel = YieldBoostConfigurationParcel(configuration), collator = mapCollatorToCollatorParcelModel(collator) ) @@ -369,11 +372,6 @@ class SetupYieldBoostViewModel( router.openConfirmYieldBoost(payload) } - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) - private fun constructActiveConfiguration(tasks: List, collator: Collator): YieldBoostConfiguration { val collatorId = collator.accountId() val collatorTask = tasks.find { it.collator.contentEquals(collatorId) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt index fcb9533afe..c5ee482861 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/payouts/confirm/ConfirmPayoutViewModel.kt @@ -26,7 +26,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.payouts.confirm. import io.novafoundation.nova.feature_staking_impl.presentation.payouts.model.mapPendingPayoutParcelToPayout import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.requireFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -101,22 +101,26 @@ class ConfirmPayoutViewModel( router.back() } - private fun sendTransactionIfValid() = feeLoaderMixin.requireFee(this) { fee -> - launch { - val asset = assetFlow.first() - val accountAddress = stakingStateFlow.first().accountAddress - val amount = asset.token.configuration.amountFromPlanks(payload.totalRewardInPlanks) - - val makePayoutPayload = MakePayoutPayload(accountAddress, fee, amount, asset, payouts) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = makePayoutPayload, - validationFailureTransformer = ::payloadValidationFailure, - progressConsumer = _showNextProgress.progressConsumer() - ) { - sendTransaction(makePayoutPayload) - } + private fun sendTransactionIfValid() = launch { + val asset = assetFlow.first() + val accountAddress = stakingStateFlow.first().accountAddress + val amount = asset.token.configuration.amountFromPlanks(payload.totalRewardInPlanks) + + val makePayoutPayload = MakePayoutPayload( + originAddress = accountAddress, + fee = feeLoaderMixin.awaitDecimalFee(), + totalReward = amount, + asset = asset, + payouts = payouts + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = makePayoutPayload, + validationFailureTransformer = ::payloadValidationFailure, + progressConsumer = _showNextProgress.progressConsumer() + ) { + sendTransaction(makePayoutPayload) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/confirm/ConfirmBondMorePayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/confirm/ConfirmBondMorePayload.kt index 458067efa1..82f39241f3 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/confirm/ConfirmBondMorePayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/confirm/ConfirmBondMorePayload.kt @@ -1,12 +1,13 @@ package io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm import android.os.Parcelable +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize import java.math.BigDecimal @Parcelize class ConfirmBondMorePayload( val amount: BigDecimal, - val fee: BigDecimal, + val fee: FeeParcelModel, val stashAddress: String, ) : Parcelable diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/confirm/ConfirmBondMoreViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/confirm/ConfirmBondMoreViewModel.kt index ba1ada79f5..10fa478f0d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/confirm/ConfirmBondMoreViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/confirm/ConfirmBondMoreViewModel.kt @@ -24,6 +24,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.bon import io.novafoundation.nova.feature_wallet_api.data.mappers.mapFeeToFeeModel import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeStatus +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -48,6 +49,8 @@ class ConfirmBondMoreViewModel( ExternalActions by externalActions, Validatable by validationExecutor { + private val decimalFee = mapFeeFromParcel(payload.fee) + private val _showNextProgress = MutableLiveData(false) val showNextProgress: LiveData = _showNextProgress @@ -68,7 +71,7 @@ class ConfirmBondMoreViewModel( .shareInBackground() val feeStatusFlow = stashAssetFlow.map { asset -> - val feeModel = mapFeeToFeeModel(payload.fee, asset.token) + val feeModel = mapFeeToFeeModel(decimalFee.genericFee, asset.token) FeeStatus.Loaded(feeModel) } @@ -94,7 +97,7 @@ class ConfirmBondMoreViewModel( private fun maybeGoToNext() = launch { val payload = BondMoreValidationPayload( stashAddress = payload.stashAddress, - fee = payload.fee, + fee = decimalFee, amount = payload.amount, stashAsset = stashAssetFlow.first() ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/select/SelectBondMoreViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/select/SelectBondMoreViewModel.kt index 1dbea6242b..9d29e40aec 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/select/SelectBondMoreViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/bond/select/SelectBondMoreViewModel.kt @@ -24,6 +24,8 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest @@ -102,37 +104,32 @@ class SelectBondMoreViewModel( ) } - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) + private fun maybeGoToNext() = launch { + _showNextProgress.value = true + + val payload = BondMoreValidationPayload( + stashAddress = stashAddress(), + fee = feeLoaderMixin.awaitDecimalFee(), + amount = amountChooserMixin.amount.first(), + stashAsset = assetFlow.first() + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { bondMoreValidationFailure(it, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer() + ) { + _showNextProgress.value = false - private fun maybeGoToNext() = requireFee { fee -> - launch { - val payload = BondMoreValidationPayload( - stashAddress = stashAddress(), - fee = fee, - amount = amountChooserMixin.amount.first(), - stashAsset = assetFlow.first() - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { bondMoreValidationFailure(it, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer() - ) { - _showNextProgress.value = false - - openConfirm(payload) - } + openConfirm(payload) } } private fun openConfirm(validationPayload: BondMoreValidationPayload) { val confirmPayload = ConfirmBondMorePayload( amount = validationPayload.amount, - fee = validationPayload.fee, + fee = mapFeeToParcel(validationPayload.fee), stashAddress = validationPayload.stashAddress, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerPayload.kt index 834b0b94ad..385b742f66 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerPayload.kt @@ -1,12 +1,13 @@ package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm import android.os.Parcelable +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize import java.math.BigDecimal @Parcelize class ConfirmSetControllerPayload( - val fee: BigDecimal, + val fee: FeeParcelModel, val stashAddress: String, val controllerAddress: String, val transferable: BigDecimal diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerViewModel.kt index 868208b0a5..655ac315ba 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerViewModel.kt @@ -20,6 +20,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.set.bondSetControllerValidationFailure import io.novafoundation.nova.feature_wallet_api.data.mappers.mapFeeToFeeModel import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeStatus +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.MutableStateFlow @@ -42,12 +43,14 @@ class ConfirmSetControllerViewModel( Validatable by validationExecutor, ExternalActions by externalActions { + private val decimalFee = mapFeeFromParcel(payload.fee) + private val assetFlow = interactor.assetFlow(payload.stashAddress) .inBackground() .share() val feeStatusFlow = assetFlow.map { asset -> - val feeModel = mapFeeToFeeModel(payload.fee, asset.token) + val feeModel = mapFeeToFeeModel(decimalFee.genericFee, asset.token) FeeStatus.Loaded(feeModel) } @@ -90,7 +93,7 @@ class ConfirmSetControllerViewModel( val payload = SetControllerValidationPayload( stashAddress = payload.stashAddress, controllerAddress = payload.controllerAddress, - fee = payload.fee, + fee = decimalFee, transferable = payload.transferable ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt index cbbda995d1..2e3d7bc762 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt @@ -35,7 +35,8 @@ import io.novafoundation.nova.feature_staking_impl.domain.validations.controller import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm.ConfirmSetControllerPayload import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.requireFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.Flow @@ -199,34 +200,34 @@ class SetControllerViewModel( private suspend fun controllerAddress() = accountStakingFlow.first().controllerAddress - private fun maybeGoToConfirm() = feeLoaderMixin.requireFee(this) { fee -> - launch { - val controllerAddress = getNewControllerAddress() + private fun maybeGoToConfirm() = launch { + validationInProgress.value = true - val payload = SetControllerValidationPayload( - stashAddress = stashAddress(), - controllerAddress = controllerAddress, - fee = fee, - transferable = assetFlow.first().transferable - ) + val controllerAddress = getNewControllerAddress() + + val payload = SetControllerValidationPayload( + stashAddress = stashAddress(), + controllerAddress = controllerAddress, + fee = feeLoaderMixin.awaitDecimalFee(), + transferable = assetFlow.first().transferable + ) - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - progressConsumer = validationInProgress.progressConsumer(), - validationFailureTransformer = { bondSetControllerValidationFailure(it, resourceManager) } - ) { - validationInProgress.value = false - - openConfirm( - ConfirmSetControllerPayload( - fee = fee, - stashAddress = payload.stashAddress, - controllerAddress = payload.controllerAddress, - transferable = payload.transferable - ) + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + progressConsumer = validationInProgress.progressConsumer(), + validationFailureTransformer = { bondSetControllerValidationFailure(it, resourceManager) } + ) { + validationInProgress.value = false + + openConfirm( + ConfirmSetControllerPayload( + fee = mapFeeToParcel(it.fee), + stashAddress = payload.stashAddress, + controllerAddress = payload.controllerAddress, + transferable = payload.transferable ) - } + ) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt index b007228e05..cc5a82b565 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/confirm/ConfirmRebondViewModel.kt @@ -24,7 +24,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.rebondValidationFailure import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.requireFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -116,22 +116,22 @@ class ConfirmRebondViewModel( ) } - private fun maybeGoToNext() = feeLoaderMixin.requireFee(this) { fee -> - launch { - val payload = RebondValidationPayload( - fee = fee, - rebondAmount = payload.amount, - controllerAsset = assetFlow.first() - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { rebondValidationFailure(it, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer(), - block = ::sendTransaction - ) - } + private fun maybeGoToNext() = launch { + _showNextProgress.value = true + + val payload = RebondValidationPayload( + fee = feeLoaderMixin.awaitDecimalFee(), + rebondAmount = payload.amount, + controllerAsset = assetFlow.first() + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { rebondValidationFailure(it, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer(), + block = ::sendTransaction + ) } private fun sendTransaction(validPayload: RebondValidationPayload) = launch { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/custom/CustomRebondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/custom/CustomRebondViewModel.kt index 6d0dce006a..8c980c1d70 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/custom/CustomRebondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rebond/custom/CustomRebondViewModel.kt @@ -22,7 +22,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.requireFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.transferableAmountModelOf import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first @@ -104,22 +104,20 @@ class CustomRebondViewModel( ) } - private fun maybeGoToNext() = feeLoaderMixin.requireFee(this) { fee -> - launch { - val payload = RebondValidationPayload( - fee = fee, - rebondAmount = amountChooserMixin.amount.first(), - controllerAsset = assetFlow.first() - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { rebondValidationFailure(it, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer(), - block = ::openConfirm - ) - } + private fun maybeGoToNext() = launch { + val payload = RebondValidationPayload( + fee = feeLoaderMixin.awaitDecimalFee(), + rebondAmount = amountChooserMixin.amount.first(), + controllerAsset = assetFlow.first() + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { rebondValidationFailure(it, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer(), + block = ::openConfirm + ) } private fun openConfirm(validPayload: RebondValidationPayload) { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/redeem/RedeemViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/redeem/RedeemViewModel.kt index a0db183f44..76ebc73767 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/redeem/RedeemViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/redeem/RedeemViewModel.kt @@ -20,6 +20,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.validations.reedeem.Re import io.novafoundation.nova.feature_staking_impl.domain.validations.reedeem.RedeemValidationSystem import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -28,7 +29,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import java.math.BigDecimal class RedeemViewModel( private val router: StakingRouter, @@ -98,28 +98,23 @@ class RedeemViewModel( ) } - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) + private fun maybeGoToNext() = launch { + _showNextProgress.value = true - private fun maybeGoToNext() = requireFee { fee -> - launch { - val asset = assetFlow.first() - - val validationPayload = RedeemValidationPayload( - fee = fee, - asset = asset - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = validationPayload, - validationFailureTransformer = { redeemValidationFailure(it, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer() - ) { - sendTransaction(it) - } + val asset = assetFlow.first() + + val validationPayload = RedeemValidationPayload( + fee = feeLoaderMixin.awaitDecimalFee(), + asset = asset + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = validationPayload, + validationFailureTransformer = { redeemValidationFailure(it, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer() + ) { + sendTransaction(it) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/confirm/ConfirmRewardDestinationViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/confirm/ConfirmRewardDestinationViewModel.kt index ae2dd95fc2..23471c377f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/confirm/ConfirmRewardDestinationViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/confirm/ConfirmRewardDestinationViewModel.kt @@ -29,6 +29,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDe import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.select.rewardDestinationValidationFailure import io.novafoundation.nova.feature_wallet_api.data.mappers.mapFeeToFeeModel import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeStatus +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.filterIsInstance @@ -53,6 +54,8 @@ class ConfirmRewardDestinationViewModel( Validatable by validationExecutor, ExternalActions by externalActions { + private val decimalFee = mapFeeFromParcel(payload.fee) + private val _showNextProgress = MutableLiveData(false) val showNextProgress: LiveData = _showNextProgress @@ -78,7 +81,7 @@ class ConfirmRewardDestinationViewModel( .shareInBackground() val feeStatusFlow = controllerAssetFlow.map { - FeeStatus.Loaded(mapFeeToFeeModel(payload.fee, it.token)) + FeeStatus.Loaded(mapFeeToFeeModel(decimalFee.genericFee, it.token)) } .shareInBackground() @@ -128,7 +131,7 @@ class ConfirmRewardDestinationViewModel( val payload = RewardDestinationValidationPayload( availableControllerBalance = controllerAsset.transferable, - fee = payload.fee, + fee = decimalFee, stashState = stashState ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/confirm/parcel/ConfirmRewardDestinationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/confirm/parcel/ConfirmRewardDestinationPayload.kt index ced4b0a2aa..e7dec66bcf 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/confirm/parcel/ConfirmRewardDestinationPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/confirm/parcel/ConfirmRewardDestinationPayload.kt @@ -1,11 +1,11 @@ package io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.confirm.parcel import android.os.Parcelable +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize -import java.math.BigDecimal @Parcelize class ConfirmRewardDestinationPayload( - val fee: BigDecimal, + val fee: FeeParcelModel, val rewardDestination: RewardDestinationParcelModel, ) : Parcelable diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/select/SelectRewardDestinationViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/select/SelectRewardDestinationViewModel.kt index e8c0bd8e5b..dd1cecdc08 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/select/SelectRewardDestinationViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/rewardDestination/select/SelectRewardDestinationViewModel.kt @@ -25,6 +25,9 @@ import io.novafoundation.nova.feature_staking_impl.presentation.common.rewardDes import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.confirm.parcel.ConfirmRewardDestinationPayload import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.confirm.parcel.RewardDestinationParcelModel import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.runtime.state.selectedOption import kotlinx.coroutines.async import kotlinx.coroutines.flow.combine @@ -35,7 +38,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import java.math.BigDecimal class SelectRewardDestinationViewModel( private val router: StakingRouter, @@ -111,35 +113,32 @@ class SelectRewardDestinationViewModel( ) } - private fun maybeGoToNext() = requireFee { fee -> - launch { - val payload = RewardDestinationValidationPayload( - availableControllerBalance = controllerAssetFlow.first().transferable, - fee = fee, - stashState = stashStateFlow.first() - ) - - val rewardDestination = rewardDestinationModelFlow.first() - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { rewardDestinationValidationFailure(resourceManager, it) }, - progressConsumer = _showNextProgress.progressConsumer() - ) { - _showNextProgress.value = false - - goToNextStep(rewardDestination, it.fee) - } + private fun maybeGoToNext() = launch { + _showNextProgress.value = true + + val payload = RewardDestinationValidationPayload( + availableControllerBalance = controllerAssetFlow.first().transferable, + fee = feeLoaderMixin.awaitDecimalFee(), + stashState = stashStateFlow.first() + ) + + val rewardDestination = rewardDestinationModelFlow.first() + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { rewardDestinationValidationFailure(resourceManager, it) }, + progressConsumer = _showNextProgress.progressConsumer() + ) { + _showNextProgress.value = false + + goToNextStep(rewardDestination, it.fee) } } - private fun goToNextStep( - rewardDestination: RewardDestinationModel, - fee: BigDecimal - ) { + private fun goToNextStep(rewardDestination: RewardDestinationModel, fee: DecimalFee) { val payload = ConfirmRewardDestinationPayload( - fee = fee, + fee = mapFeeToParcel(fee), rewardDestination = mapRewardDestinationModelToRewardDestinationParcelModel(rewardDestination) ) @@ -153,10 +152,5 @@ class SelectRewardDestinationViewModel( } } - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) - private suspend fun rewardCalculator() = rewardCalculator.await() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/ConfirmMultiStakingViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/ConfirmMultiStakingViewModel.kt index 2eb5f070ec..c19ff42e87 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/ConfirmMultiStakingViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/ConfirmMultiStakingViewModel.kt @@ -104,7 +104,7 @@ class ConfirmMultiStakingViewModel( .shareInBackground() val feeStatusFlow = assetFlow.map { asset -> - val feeModel = mapFeeToFeeModel(decimalFee.fee, asset.token) + val feeModel = mapFeeToFeeModel(decimalFee.genericFee, asset.token) FeeStatus.Loaded(feeModel) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/confirm/ConfirmUnbondPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/confirm/ConfirmUnbondPayload.kt index 61c7a4d3a0..846e3ed74a 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/confirm/ConfirmUnbondPayload.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/confirm/ConfirmUnbondPayload.kt @@ -1,11 +1,12 @@ package io.novafoundation.nova.feature_staking_impl.presentation.staking.unbond.confirm import android.os.Parcelable +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import kotlinx.android.parcel.Parcelize import java.math.BigDecimal @Parcelize class ConfirmUnbondPayload( val amount: BigDecimal, - val fee: BigDecimal + val fee: FeeParcelModel ) : Parcelable diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/confirm/ConfirmUnbondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/confirm/ConfirmUnbondViewModel.kt index 71a1bbbb1b..0073a07f6e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/confirm/ConfirmUnbondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/confirm/ConfirmUnbondViewModel.kt @@ -26,6 +26,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.staking.unbond.u import io.novafoundation.nova.feature_wallet_api.data.mappers.mapFeeToFeeModel import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeStatus +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain @@ -52,6 +53,8 @@ class ConfirmUnbondViewModel( ExternalActions by externalActions, Validatable by validationExecutor { + private val decimalFee = mapFeeFromParcel(payload.fee) + private val _showNextProgress = MutableLiveData(false) val showNextProgress: LiveData = _showNextProgress @@ -77,7 +80,7 @@ class ConfirmUnbondViewModel( .shareInBackground() val feeStatusLiveData = assetFlow.map { asset -> - val feeModel = mapFeeToFeeModel(payload.fee, asset.token) + val feeModel = mapFeeToFeeModel(decimalFee.genericFee, asset.token) FeeStatus.Loaded(feeModel) } @@ -111,7 +114,7 @@ class ConfirmUnbondViewModel( val payload = UnbondValidationPayload( asset = asset, stash = accountStakingFlow.first(), - fee = payload.fee, + fee = decimalFee, amount = payload.amount, ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/select/SelectUnbondViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/select/SelectUnbondViewModel.kt index 55fe146265..09a2044f1a 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/select/SelectUnbondViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/unbond/select/SelectUnbondViewModel.kt @@ -22,6 +22,8 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChooser.AmountChooserMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel import io.novafoundation.nova.feature_wallet_api.presentation.model.transferableAmountModelOf import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first @@ -100,39 +102,34 @@ class SelectUnbondViewModel( ) } - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) + private fun maybeGoToNext() = launch { + _showNextProgress.value = true + + val asset = assetFlow.first() + + val payload = UnbondValidationPayload( + stash = accountStakingFlow.first(), + asset = asset, + fee = feeLoaderMixin.awaitDecimalFee(), + amount = amountMixin.amount.first(), + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformerCustom = { status, flowActions -> unbondValidationFailure(status, flowActions, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer() + ) { correctPayload -> + _showNextProgress.value = false - private fun maybeGoToNext() = requireFee { fee -> - launch { - val asset = assetFlow.first() - - val payload = UnbondValidationPayload( - stash = accountStakingFlow.first(), - asset = asset, - fee = fee, - amount = amountMixin.amount.first(), - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformerCustom = { status, flowActions -> unbondValidationFailure(status, flowActions, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer() - ) { correctPayload -> - _showNextProgress.value = false - - openConfirm(correctPayload) - } + openConfirm(correctPayload) } } private fun openConfirm(validationPayload: UnbondValidationPayload) { val confirmUnbondPayload = ConfirmUnbondPayload( amount = validationPayload.amount, - fee = validationPayload.fee + fee = mapFeeToParcel(validationPayload.fee) ) router.openConfirmUnbond(confirmUnbondPayload) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt index 4f66ebedea..02e102d352 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt @@ -34,6 +34,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.common.validatio import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.confirm.hints.ConfirmStakeHintsMixinFactory import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.filterIsInstance @@ -41,7 +42,6 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -import java.math.BigDecimal class ConfirmChangeValidatorsViewModel( private val router: StakingRouter, @@ -135,21 +135,21 @@ class ConfirmChangeValidatorsViewModel( private fun prepareNominations() = currentProcessState.validators.map(Validator::accountIdHex) - private fun sendTransactionIfValid() = requireFee { fee -> - launch { - val payload = SetupStakingPayload( - maxFee = fee, - controllerAsset = controllerAssetFlow.first() - ) - - validationExecutor.requireValid( - validationSystem = validationSystem, - payload = payload, - validationFailureTransformer = { stakingValidationFailure(it, resourceManager) }, - progressConsumer = _showNextProgress.progressConsumer() - ) { - sendTransaction() - } + private fun sendTransactionIfValid() = launch { + _showNextProgress.value = true + + val payload = SetupStakingPayload( + maxFee = feeLoaderMixin.awaitDecimalFee(), + controllerAsset = controllerAssetFlow.first() + ) + + validationExecutor.requireValid( + validationSystem = validationSystem, + payload = payload, + validationFailureTransformer = { stakingValidationFailure(it, resourceManager) }, + progressConsumer = _showNextProgress.progressConsumer() + ) { + sendTransaction() } } @@ -172,11 +172,6 @@ class ConfirmChangeValidatorsViewModel( } } - private fun requireFee(block: (BigDecimal) -> Unit) = feeLoaderMixin.requireFee( - block, - onError = { title, message -> showError(title, message) } - ) - private suspend fun generateDestinationModel(address: String, name: String?): AddressModel { return addressIconGenerator.createAddressModel( accountAddress = address, diff --git a/feature-swap-api/src/main/java/io/novafoundation/nova/feature_swap_api/domain/model/SwapQuote.kt b/feature-swap-api/src/main/java/io/novafoundation/nova/feature_swap_api/domain/model/SwapQuote.kt index 21f714bc52..1e827dd495 100644 --- a/feature-swap-api/src/main/java/io/novafoundation/nova/feature_swap_api/domain/model/SwapQuote.kt +++ b/feature-swap-api/src/main/java/io/novafoundation/nova/feature_swap_api/domain/model/SwapQuote.kt @@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_swap_api.domain.model import io.novafoundation.nova.common.utils.Percent import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.data.model.amountByRequestedAccount import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.model.ChainAssetWithAmount import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks @@ -66,7 +67,7 @@ class SwapFee( ) : GenericFee val SwapFee.totalDeductedPlanks: Balance - get() = networkFee.amount + minimumBalanceBuyIn.commissionAssetToSpendOnBuyIn + get() = networkFee.amountByRequestedAccount + minimumBalanceBuyIn.commissionAssetToSpendOnBuyIn sealed class MinimumBalanceBuyIn { diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt index e55eae1416..003eb1e32d 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/assetExchange/assetConversion/AssetConversionExchange.kt @@ -11,7 +11,7 @@ import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.Tran import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.data.model.InlineFee +import io.novafoundation.nova.feature_account_api.data.model.SubstrateFee import io.novafoundation.nova.feature_swap_api.domain.model.MinimumBalanceBuyIn import io.novafoundation.nova.feature_swap_api.domain.model.SlippageConfig import io.novafoundation.nova.feature_swap_api.domain.model.SwapDirection @@ -165,6 +165,9 @@ private class AssetConversionExchange( } } + // TODO we purposefully do not use `nativeTokenFee.amountByRequestedAccount` + // since we have disabled fee payment in custom tokens for accounts where the difference matters (e.g. proxy) + // We should adapt it if we decide to remove the restriction private suspend fun calculateCustomTokenFee( nativeTokenFee: Fee, nativeAsset: Asset, @@ -197,7 +200,7 @@ private class AssetConversionExchange( } return AssetExchangeFee( - networkFee = InlineFee(toBuyNativeFee), + networkFee = SubstrateFee(toBuyNativeFee, nativeTokenFee.submissionOrigin), minimumBalanceBuyIn = minimumBalanceBuyIn ) } diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/repository/SwapTransactionHistoryRepository.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/repository/SwapTransactionHistoryRepository.kt index cc1afc6a07..ffac578b81 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/repository/SwapTransactionHistoryRepository.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/data/repository/SwapTransactionHistoryRepository.kt @@ -42,6 +42,7 @@ class RealSwapTransactionHistoryRepository( hash = txSubmission.hash, originAddress = chain.addressOf(txSubmission.submissionOrigin.requestedOrigin), assetId = chainAsset.localId, + // Insert fee regardless of who actually paid it fee = feeAsset.withAmountLocal(fee.networkFee.amount), amountIn = assetIn.withAmountLocal(swapLimit.expectedAmountIn), amountOut = assetOut.withAmountLocal(swapLimit.expectedAmountOut), diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/di/SwapFeatureModule.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/di/SwapFeatureModule.kt index 5c5e510610..37d1b29f83 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/di/SwapFeatureModule.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/di/SwapFeatureModule.kt @@ -13,13 +13,13 @@ import io.novafoundation.nova.feature_buy_api.domain.BuyTokenRegistry import io.novafoundation.nova.feature_swap_api.domain.interactor.SwapAvailabilityInteractor import io.novafoundation.nova.feature_swap_api.domain.swap.SwapService import io.novafoundation.nova.feature_swap_api.presentation.formatters.SwapRateFormatter +import io.novafoundation.nova.feature_swap_api.presentation.state.SwapSettingsStateProvider import io.novafoundation.nova.feature_swap_impl.data.assetExchange.assetConversion.AssetConversionExchangeFactory import io.novafoundation.nova.feature_swap_impl.data.network.blockhain.updaters.SwapUpdateSystemFactory import io.novafoundation.nova.feature_swap_impl.data.repository.RealSwapTransactionHistoryRepository import io.novafoundation.nova.feature_swap_impl.data.repository.SwapTransactionHistoryRepository -import io.novafoundation.nova.feature_swap_impl.domain.interactor.SwapInteractor -import io.novafoundation.nova.feature_swap_api.presentation.state.SwapSettingsStateProvider import io.novafoundation.nova.feature_swap_impl.domain.interactor.RealSwapAvailabilityInteractor +import io.novafoundation.nova.feature_swap_impl.domain.interactor.SwapInteractor import io.novafoundation.nova.feature_swap_impl.domain.swap.RealSwapService import io.novafoundation.nova.feature_swap_impl.presentation.common.PriceImpactFormatter import io.novafoundation.nova.feature_swap_impl.presentation.common.RealPriceImpactFormatter @@ -69,9 +69,10 @@ class SwapFeatureModule { fun provideSwapService( assetConversionExchangeFactory: AssetConversionExchangeFactory, computationalCache: ComputationalCache, - chainRegistry: ChainRegistry + chainRegistry: ChainRegistry, + accountRepository: AccountRepository ): SwapService { - return RealSwapService(assetConversionExchangeFactory, computationalCache, chainRegistry) + return RealSwapService(assetConversionExchangeFactory, computationalCache, chainRegistry, accountRepository) } @Provides diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/interactor/SwapInteractor.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/interactor/SwapInteractor.kt index 28149e90a5..07bde098af 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/interactor/SwapInteractor.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/interactor/SwapInteractor.kt @@ -40,6 +40,7 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.A import io.novafoundation.nova.feature_wallet_api.domain.interfaces.CrossChainTransfersUseCase import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.interfaces.incomingCrossChainDirectionsAvailable +import io.novafoundation.nova.feature_wallet_api.presentation.model.GenericDecimalFee import io.novafoundation.nova.runtime.ext.commissionAsset import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain @@ -88,20 +89,23 @@ class SwapInteractor( return swapService.quote(quoteArgs) } - suspend fun executeSwap(swapExecuteArgs: SwapExecuteArgs, swapFee: SwapFee): Result = withContext(Dispatchers.IO) { + suspend fun executeSwap( + swapExecuteArgs: SwapExecuteArgs, + decimalFee: GenericDecimalFee + ): Result = withContext(Dispatchers.IO) { swapService.swap(swapExecuteArgs) .onSuccess { submission -> swapTransactionHistoryRepository.insertPendingSwap( chainAsset = swapExecuteArgs.assetIn, swapArgs = swapExecuteArgs, - fee = swapFee, + fee = decimalFee.genericFee, txSubmission = submission ) swapTransactionHistoryRepository.insertPendingSwap( chainAsset = swapExecuteArgs.assetOut, swapArgs = swapExecuteArgs, - fee = swapFee, + fee = decimalFee.genericFee, txSubmission = submission ) } @@ -174,7 +178,7 @@ class SwapInteractor( feeAsset: Chain.Asset, quoteArgs: SwapQuoteArgs, swapQuote: SwapQuote, - swapFee: SwapFee + swapFee: GenericDecimalFee ): SwapValidationPayload? { val metaAccount = accountRepository.getSelectedMetaAccount() val chainIn = chainRegistry.getChain(swapQuote.assetIn.chainId) @@ -199,7 +203,7 @@ class SwapInteractor( ), slippage = quoteArgs.slippage, feeAsset = walletRepository.getAsset(metaAccount.id, feeAsset) ?: return null, - swapFee = swapFee, + decimalFee = swapFee, swapQuote = swapQuote, swapQuoteArgs = quoteArgs, swapExecuteArgs = executeArgs diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/swap/RealSwapService.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/swap/RealSwapService.kt index 67af34e1a6..36fb88693e 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/swap/RealSwapService.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/swap/RealSwapService.kt @@ -13,6 +13,8 @@ import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.common.utils.isZero import io.novafoundation.nova.common.utils.toPercent import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.requestedAccountPaysFees import io.novafoundation.nova.feature_swap_api.domain.model.SlippageConfig import io.novafoundation.nova.feature_swap_api.domain.model.SwapDirection import io.novafoundation.nova.feature_swap_api.domain.model.SwapExecuteArgs @@ -48,7 +50,8 @@ private const val EXCHANGES_CACHE = "RealSwapService.EXCHANGES" internal class RealSwapService( private val assetConversionFactory: AssetConversionExchangeFactory, private val computationalCache: ComputationalCache, - private val chainRegistry: ChainRegistry + private val chainRegistry: ChainRegistry, + private val accountRepository: AccountRepository, ) : SwapService { override suspend fun canPayFeeInNonUtilityAsset(asset: Chain.Asset): Boolean = withContext(Dispatchers.Default) { @@ -56,8 +59,11 @@ internal class RealSwapService( val exchange = exchanges(computationScope).getValue(asset.chainId) val isCustomFeeToken = !asset.isCommissionAsset + val currentMetaAccount = accountRepository.getSelectedMetaAccount() - isCustomFeeToken && exchange.canPayFeeInNonUtilityToken(asset) + // TODO we disable custom fee tokens payment for account types where current account is not the one who pays fees (e.g. it is proxied). + // This restriction can be removed once we consider all corner-cases + isCustomFeeToken && exchange.canPayFeeInNonUtilityToken(asset) && currentMetaAccount.type.requestedAccountPaysFees() } override suspend fun assetsAvailableForSwap( diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientBalanceConsideringNonSufficientAssetsValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientBalanceConsideringNonSufficientAssetsValidation.kt index 66dd6bee59..38e7960e03 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientBalanceConsideringNonSufficientAssetsValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SufficientBalanceConsideringNonSufficientAssetsValidation.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_swap_impl.domain.validation import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.valid import io.novafoundation.nova.common.validation.validOrError +import io.novafoundation.nova.feature_account_api.data.model.amountByRequestedAccount import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.isSelfSufficientAsset @@ -21,7 +22,7 @@ class SufficientBalanceConsideringNonSufficientAssetsValidation( if (!isSelfSufficientAssetOut && assetIn.token.configuration.isCommissionAsset) { val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(value.detailedAssetIn.chain, assetIn.token.configuration) - val fee = value.swapFee.networkFee.amount + val fee = value.decimalFee.networkFee.amountByRequestedAccount return validOrError(assetIn.balanceCountedTowardsEDInPlanks - existentialDeposit >= amount + fee) { SwapValidationFailure.InsufficientBalance.BalanceNotConsiderInsufficientReceiveAsset( diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapPayloadValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapPayloadValidation.kt index 11a34c61e3..b585901d6e 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapPayloadValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapPayloadValidation.kt @@ -9,6 +9,7 @@ import io.novafoundation.nova.feature_swap_api.domain.model.commissionAssetToSpe import io.novafoundation.nova.feature_swap_api.domain.model.totalDeductedPlanks import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.model.Asset +import io.novafoundation.nova.feature_wallet_api.presentation.model.GenericDecimalFee import io.novafoundation.nova.runtime.ext.fullId import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigInteger @@ -18,7 +19,7 @@ data class SwapValidationPayload( val detailedAssetOut: SwapAssetData, val slippage: Percent, val feeAsset: Asset, - val swapFee: SwapFee, + val decimalFee: GenericDecimalFee, val swapQuote: SwapQuote, val swapQuoteArgs: SwapQuoteArgs, val swapExecuteArgs: SwapExecuteArgs @@ -43,14 +44,14 @@ val SwapValidationPayload.swapAmountInFeeToken: Balance val SwapValidationPayload.toBuyAmountToKeepMainEDInFeeAsset: Balance get() = if (isFeePayingByAssetIn) { - swapFee.minimumBalanceBuyIn.commissionAssetToSpendOnBuyIn + decimalFee.genericFee.minimumBalanceBuyIn.commissionAssetToSpendOnBuyIn } else { BigInteger.ZERO } val SwapValidationPayload.totalDeductedAmountInFeeToken: Balance get() = if (isFeePayingByAssetIn) { - swapFee.totalDeductedPlanks + decimalFee.genericFee.totalDeductedPlanks } else { BigInteger.ZERO } diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapValidations.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapValidations.kt index 0a74e1da41..5f4e7b206b 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapValidations.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/SwapValidations.kt @@ -16,6 +16,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.validation.checkForFeeCh import io.novafoundation.nova.feature_wallet_api.domain.validation.positiveAmount import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalanceConsideringConsumersValidation +import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalanceGeneric import java.math.BigDecimal typealias SwapValidationSystem = ValidationSystem @@ -51,7 +52,7 @@ fun SwapValidationSystemBuilder.sufficientBalanceConsideringConsumersValidation( SwapValidationFailure.InsufficientBalance.BalanceNotConsiderConsumers( nativeAsset = payload.detailedAssetIn.asset.token.configuration, feeAsset = payload.feeAsset.token.configuration, - swapFee = payload.swapFee, + swapFee = payload.decimalFee.genericFee, existentialDeposit = existentialDeposit ) } @@ -65,20 +66,18 @@ fun SwapValidationSystemBuilder.enoughLiquidity(sharedQuoteValidationRetriever: SwapEnoughLiquidityValidation { sharedQuoteValidationRetriever.retrieveQuote(it) } ) -fun SwapValidationSystemBuilder.sufficientBalanceInFeeAsset() = sufficientBalance( +fun SwapValidationSystemBuilder.sufficientBalanceInFeeAsset() = sufficientBalanceGeneric( available = { it.feeAsset.transferable }, amount = { BigDecimal.ZERO }, - fee = { it.feeAsset.token.amountFromPlanks(it.swapFee.networkFee.amount) }, - error = { _, _ -> SwapValidationFailure.NotEnoughFunds.ToPayFee } + fee = { it.decimalFee }, + error = { SwapValidationFailure.NotEnoughFunds.ToPayFee } ) fun SwapValidationSystemBuilder.sufficientBalanceInUsedAsset() = sufficientBalance( available = { it.detailedAssetIn.asset.transferable }, amount = { it.detailedAssetIn.asset.token.amountFromPlanks(it.detailedAssetIn.amountInPlanks) }, - fee = { BigDecimal.ZERO }, - error = { _, _ -> - SwapValidationFailure.NotEnoughFunds.InUsedAsset - } + fee = { null }, + error = { SwapValidationFailure.NotEnoughFunds.InUsedAsset } ) fun SwapValidationSystemBuilder.sufficientAssetOutBalanceToStayAboveED( @@ -89,7 +88,7 @@ fun SwapValidationSystemBuilder.checkForFeeChanges( swapService: SwapService ) = checkForFeeChanges( calculateFee = { swapService.estimateFee(it.swapExecuteArgs) }, - currentFee = { it.feeAsset.token.amountFromPlanks(it.swapFee.networkFee.amount) }, + currentFee = { it.decimalFee }, chainAsset = { it.feeAsset.token.configuration }, error = SwapValidationFailure::FeeChangeDetected ) diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/EnoughNativeAssetBalanceToPayFeeConsideringEDValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/EnoughNativeAssetBalanceToPayFeeConsideringEDValidation.kt index 2422a9bded..2dd5ad8e99 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/EnoughNativeAssetBalanceToPayFeeConsideringEDValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/EnoughNativeAssetBalanceToPayFeeConsideringEDValidation.kt @@ -3,6 +3,7 @@ package io.novafoundation.nova.feature_swap_impl.domain.validation.validations import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.valid import io.novafoundation.nova.common.validation.validOrError +import io.novafoundation.nova.feature_account_api.data.model.amountByRequestedAccount import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidation import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.NotEnoughFunds @@ -24,7 +25,7 @@ class EnoughNativeAssetBalanceToPayFeeConsideringEDValidation( if (feeChainAsset.isCommissionAsset) { val chain = chainRegistry.getChain(feeChainAsset.chainId) val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chain, feeChainAsset) - return validOrError(value.feeAsset.balanceCountedTowardsEDInPlanks - value.swapFee.networkFee.amount >= existentialDeposit) { + return validOrError(value.feeAsset.balanceCountedTowardsEDInPlanks - value.decimalFee.networkFee.amountByRequestedAccount >= existentialDeposit) { NotEnoughFunds.ToPayFeeAndStayAboveED(value.feeAsset.token.configuration) } } diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapFeeSufficientBalanceValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapFeeSufficientBalanceValidation.kt index d8141e3f74..bd694f1636 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapFeeSufficientBalanceValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapFeeSufficientBalanceValidation.kt @@ -28,16 +28,16 @@ class SwapFeeSufficientBalanceValidation : SwapValidation { val toBuyAmountToKeepEDInFeeAsset = value.toBuyAmountToKeepMainEDInFeeAsset return if (toBuyAmountToKeepEDInFeeAsset.isZero) { - InsufficientBalance.NoNeedsToBuyMainAssetED(chainAssetIn, feeAsset, maxAmountToSwap, value.swapFee.networkFee).validationError() + InsufficientBalance.NoNeedsToBuyMainAssetED(chainAssetIn, feeAsset, maxAmountToSwap, value.decimalFee.networkFee).validationError() } else { InsufficientBalance.NeedsToBuyMainAssetED( value.feeAsset.token.configuration, chainAssetIn, - value.swapFee.minimumBalanceBuyIn.requireNativeAsset(), - toBuyAmountToKeepEDInCommissionAsset = value.swapFee.minimumBalanceBuyIn.nativeMinimumBalance, + value.decimalFee.genericFee.minimumBalanceBuyIn.requireNativeAsset(), + toBuyAmountToKeepEDInCommissionAsset = value.decimalFee.genericFee.minimumBalanceBuyIn.nativeMinimumBalance, toSellAmountToKeepEDUsingAssetIn = toBuyAmountToKeepEDInFeeAsset, maxAmountToSwap, - value.swapFee.networkFee + value.decimalFee.networkFee ).validationError() } } diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapSmallRemainingBalanceValidation.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapSmallRemainingBalanceValidation.kt index c166f3313d..66cc8fa729 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapSmallRemainingBalanceValidation.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/domain/validation/validations/SwapSmallRemainingBalanceValidation.kt @@ -39,12 +39,12 @@ class SwapSmallRemainingBalanceValidation( TooSmallRemainingBalance.NeedsToBuyMainAssetED( feeChainAsset, chainAssetIn, - value.swapFee.minimumBalanceBuyIn.requireNativeAsset(), + value.decimalFee.genericFee.minimumBalanceBuyIn.requireNativeAsset(), assetInExistentialDeposit, - toBuyAmountToKeepEDInCommissionAsset = value.swapFee.minimumBalanceBuyIn.nativeMinimumBalance, + toBuyAmountToKeepEDInCommissionAsset = value.decimalFee.genericFee.minimumBalanceBuyIn.nativeMinimumBalance, toSellAmountToKeepEDUsingAssetIn = toBuyAmountToKeepEDInFeeAsset, remainingBalance, - value.swapFee.networkFee + value.decimalFee.networkFee ).validationError() } } diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/SwapConfirmationViewModel.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/SwapConfirmationViewModel.kt index 1bf33eb0c7..df5bacff48 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/SwapConfirmationViewModel.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/SwapConfirmationViewModel.kt @@ -60,7 +60,7 @@ import io.novafoundation.nova.feature_swap_impl.presentation.mixin.maxAction.Max import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeStatus import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.GenericFeeLoaderMixin -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.getFeeOrNull +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.getDecimalFeeOrNull import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountModel import io.novafoundation.nova.feature_wallet_api.presentation.model.fullChainAssetId import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel @@ -253,7 +253,7 @@ class SwapConfirmationViewModel( } private fun executeSwap(validationPayload: SwapValidationPayload) = launch { - swapInteractor.executeSwap(validationPayload.swapExecuteArgs, validationPayload.swapFee) + swapInteractor.executeSwap(validationPayload.swapExecuteArgs, validationPayload.decimalFee) .onSuccess { navigateToNextScreen(validationPayload.swapExecuteArgs.assetIn) } .onFailure(::showError) @@ -310,7 +310,7 @@ class SwapConfirmationViewModel( private suspend fun getValidationPayload(): SwapValidationPayload? { val confirmationState = confirmationStateFlow.value ?: return null - val swapFee = feeMixin.getFeeOrNull() ?: return null + val swapFee = feeMixin.getDecimalFeeOrNull() ?: return null return swapInteractor.getValidationPayload( assetIn = confirmationState.swapQuote.assetIn, assetOut = confirmationState.swapQuote.assetOut, diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/payload/SwapConfirmationPayload.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/payload/SwapConfirmationPayload.kt index 175f6881d2..71d86d2da6 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/payload/SwapConfirmationPayload.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/payload/SwapConfirmationPayload.kt @@ -3,9 +3,10 @@ package io.novafoundation.nova.feature_swap_impl.presentation.confirmation.paylo import android.os.Parcelable import io.novafoundation.nova.feature_swap_api.presentation.model.SwapDirectionModel import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload -import java.math.BigDecimal import kotlinx.android.parcel.Parcelize +import java.math.BigDecimal @Parcelize class SwapConfirmationPayload( @@ -28,7 +29,7 @@ class SwapConfirmationPayload( @Parcelize class FeeDetails( - val amount: Balance, + val networkFee: FeeParcelModel, val minimumBalanceBuyIn: MinimumBalanceBuyIn ) : Parcelable { diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/payload/SwapConfirmationPayloadFormatter.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/payload/SwapConfirmationPayloadFormatter.kt index ded17a3363..ee7e08b01c 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/payload/SwapConfirmationPayloadFormatter.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/confirmation/payload/SwapConfirmationPayloadFormatter.kt @@ -1,13 +1,15 @@ package io.novafoundation.nova.feature_swap_impl.presentation.confirmation.payload import io.novafoundation.nova.common.utils.asPercent -import io.novafoundation.nova.feature_account_api.data.model.InlineFee import io.novafoundation.nova.feature_swap_api.domain.model.MinimumBalanceBuyIn import io.novafoundation.nova.feature_swap_api.domain.model.SwapFee import io.novafoundation.nova.feature_swap_api.domain.model.SwapQuote import io.novafoundation.nova.feature_swap_api.presentation.model.mapFromModel import io.novafoundation.nova.feature_swap_api.presentation.model.mapToModel import io.novafoundation.nova.feature_wallet_api.domain.model.withAmount +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeFromParcel +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel +import io.novafoundation.nova.feature_wallet_api.presentation.model.GenericDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.fullChainAssetId import io.novafoundation.nova.feature_wallet_api.presentation.model.toAssetPayload import io.novafoundation.nova.runtime.ext.fullId @@ -53,11 +55,14 @@ class SwapConfirmationPayloadFormatter( SwapConfirmationPayload.FeeDetails.MinimumBalanceBuyIn.NoBuyInNeeded -> MinimumBalanceBuyIn.NoBuyInNeeded } - return SwapFee(InlineFee(model.amount), minimumBalanceBuyIn) + + val decimalFee = mapFeeFromParcel(model.networkFee) + + return SwapFee(decimalFee.networkFee, minimumBalanceBuyIn) } - fun mapFeeToModel(swapFee: SwapFee): SwapConfirmationPayload.FeeDetails { - val minimumBalanceBuyIn = when (val minimumBalanceBuyIn = swapFee.minimumBalanceBuyIn) { + fun mapFeeToModel(swapFee: GenericDecimalFee): SwapConfirmationPayload.FeeDetails { + val minimumBalanceBuyIn = when (val minimumBalanceBuyIn = swapFee.genericFee.minimumBalanceBuyIn) { is MinimumBalanceBuyIn.NeedsToBuyMinimumBalance -> { val nativeAsset = minimumBalanceBuyIn.nativeAsset.fullId.toAssetPayload() val commissionAsset = minimumBalanceBuyIn.commissionAsset.fullId.toAssetPayload() @@ -71,6 +76,6 @@ class SwapConfirmationPayloadFormatter( MinimumBalanceBuyIn.NoBuyInNeeded -> SwapConfirmationPayload.FeeDetails.MinimumBalanceBuyIn.NoBuyInNeeded } - return SwapConfirmationPayload.FeeDetails(swapFee.networkFee.amount, minimumBalanceBuyIn) + return SwapConfirmationPayload.FeeDetails(mapFeeToParcel(swapFee), minimumBalanceBuyIn) } } diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/main/SwapMainSettingsViewModel.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/main/SwapMainSettingsViewModel.kt index 1952570185..dd675020c8 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/main/SwapMainSettingsViewModel.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/main/SwapMainSettingsViewModel.kt @@ -85,6 +85,7 @@ import io.novafoundation.nova.feature_wallet_api.presentation.mixin.amountChoose import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeStatus import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.GenericFeeLoaderMixin +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.loadedDecimalFeeOrNullFlow import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.loadedFeeModelOrNullFlow import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.loadedFeeOrNullFlow import io.novafoundation.nova.feature_wallet_api.presentation.model.AssetPayload @@ -773,7 +774,7 @@ class SwapMainSettingsViewModel( feeAsset = swapSettings.feeAsset ?: return null, quoteArgs = quotingState.quoteArgs, swapQuote = quotingState.value, - swapFee = feeMixin.loadedFeeOrNullFlow().first() ?: return null + swapFee = feeMixin.loadedDecimalFeeOrNullFlow().first() ?: return null ) } @@ -814,7 +815,7 @@ class SwapMainSettingsViewModel( feeAsset = validPayload.feeAsset.token.configuration.fullId.toAssetPayload(), rate = validPayload.swapQuote.swapRate(), slippage = validPayload.slippage.value, - swapFee = swapConfirmationPayloadFormatter.mapFeeToModel(validPayload.swapFee) + swapFee = swapConfirmationPayloadFormatter.mapFeeToModel(validPayload.decimalFee) ) swapRouter.openSwapConfirmation(payload) diff --git a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/main/SwapValidationFailureUi.kt b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/main/SwapValidationFailureUi.kt index 79797de558..b1f4a076ac 100644 --- a/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/main/SwapValidationFailureUi.kt +++ b/feature-swap-impl/src/main/java/io/novafoundation/nova/feature_swap_impl/presentation/main/SwapValidationFailureUi.kt @@ -7,17 +7,18 @@ import io.novafoundation.nova.common.validation.TransformedFailure import io.novafoundation.nova.common.validation.ValidationFlowActions import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.asDefault +import io.novafoundation.nova.feature_account_api.data.model.amountByRequestedAccount import io.novafoundation.nova.feature_swap_api.domain.model.SwapFee import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure -import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.TooSmallRemainingBalance +import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.AmountOutIsTooLowToStayAboveED import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.FeeChangeDetected import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.InsufficientBalance -import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.AmountOutIsTooLowToStayAboveED -import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.NotEnoughLiquidity -import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.NonPositiveAmount -import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.NewRateExceededSlippage import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.InvalidSlippage +import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.NewRateExceededSlippage +import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.NonPositiveAmount import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.NotEnoughFunds +import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.NotEnoughLiquidity +import io.novafoundation.nova.feature_swap_impl.domain.validation.SwapValidationFailure.TooSmallRemainingBalance import io.novafoundation.nova.feature_wallet_api.R import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.validation.amountIsTooBig @@ -28,9 +29,9 @@ import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatT import io.novafoundation.nova.feature_wallet_api.presentation.validation.handleInsufficientBalanceCommission import io.novafoundation.nova.feature_wallet_api.presentation.validation.handleNonPositiveAmount import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import kotlinx.coroutines.CoroutineScope import java.math.BigDecimal import java.math.BigInteger -import kotlinx.coroutines.CoroutineScope fun CoroutineScope.mapSwapValidationFailureToUI( resourceManager: ResourceManager, @@ -85,7 +86,7 @@ fun CoroutineScope.mapSwapValidationFailureToUI( message = resourceManager.getString( R.string.swap_failure_too_small_remaining_balance_with_buy_ed_message, reason.assetInExistentialDeposit.formatPlanks(reason.assetIn), - reason.fee.amount.formatPlanks(reason.feeAsset), + reason.fee.amountByRequestedAccount.formatPlanks(reason.feeAsset), reason.toSellAmountToKeepEDUsingAssetIn.formatPlanks(reason.assetIn), reason.toBuyAmountToKeepEDInCommissionAsset.formatPlanks(reason.nativeAsset), reason.nativeAsset.symbol, @@ -101,7 +102,7 @@ fun CoroutineScope.mapSwapValidationFailureToUI( message = resourceManager.getString( R.string.swap_failure_insufficient_balance_message, reason.maxSwapAmount.formatPlanks(reason.assetIn), - reason.fee.amount.formatPlanks(reason.feeAsset) + reason.fee.amountByRequestedAccount.formatPlanks(reason.feeAsset) ), resourceManager = resourceManager, positiveButtonClick = amountInSwapMaxAction @@ -112,7 +113,7 @@ fun CoroutineScope.mapSwapValidationFailureToUI( message = resourceManager.getString( R.string.swap_failure_insufficient_balance_with_buy_ed_message, reason.maxSwapAmount.formatPlanks(reason.assetIn), - reason.fee.amount.formatPlanks(reason.feeAsset), + reason.fee.amountByRequestedAccount.formatPlanks(reason.feeAsset), reason.toSellAmountToKeepEDUsingAssetIn.formatPlanks(reason.assetIn), reason.toBuyAmountToKeepEDInCommissionAsset.formatPlanks(reason.nativeAsset), reason.nativeAsset.symbol @@ -126,7 +127,7 @@ fun CoroutineScope.mapSwapValidationFailureToUI( resourceManager.getString( R.string.swap_failure_balance_not_consider_consumers, reason.existentialDeposit.formatPlanks(reason.nativeAsset), - reason.swapFee.networkFee.amount.formatPlanks(reason.feeAsset) + reason.swapFee.networkFee.amountByRequestedAccount.formatPlanks(reason.feeAsset) ) ).asDefault() diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/mappers/Fee.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/mappers/Fee.kt index c8048510e8..2f0db67ad0 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/mappers/Fee.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/mappers/Fee.kt @@ -1,17 +1,13 @@ package io.novafoundation.nova.feature_wallet_api.data.mappers import io.novafoundation.nova.feature_account_api.data.model.Fee -import io.novafoundation.nova.feature_account_api.data.model.InlineFee import io.novafoundation.nova.feature_wallet_api.domain.model.Token import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks -import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.GenericFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.SimpleFee -import io.novafoundation.nova.feature_wallet_api.presentation.model.FeeModel import io.novafoundation.nova.feature_wallet_api.presentation.model.GenericDecimalFee import io.novafoundation.nova.feature_wallet_api.presentation.model.GenericFeeModel import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel -import java.math.BigDecimal fun mapFeeToFeeModel( fee: F, @@ -37,13 +33,3 @@ fun mapFeeToFeeModel( token: Token, includeZeroFiat: Boolean = true ) = mapFeeToFeeModel(SimpleFee(fee), token, includeZeroFiat) - -@Suppress("DeprecatedCallableAddReplaceWith") -@Deprecated("Backward-compatible adapter") -fun mapFeeToFeeModel( - feeAmount: BigDecimal, - token: Token, - includeZeroFiat: Boolean = true -): FeeModel { - return mapFeeToFeeModel(InlineFee(token.planksFromAmount(feeAmount)), token, includeZeroFiat) -} diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/tranfers/AssetTransferValidations.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/tranfers/AssetTransferValidations.kt index 1309f9f421..02e9d839fb 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/tranfers/AssetTransferValidations.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/tranfers/AssetTransferValidations.kt @@ -9,6 +9,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.validation.FeeChangeDete import io.novafoundation.nova.feature_wallet_api.domain.validation.InsufficientBalanceToStayAboveEDError import io.novafoundation.nova.feature_wallet_api.domain.validation.NotEnoughToPayFeesError import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.SimpleFee +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import io.novafoundation.nova.runtime.ext.commissionAsset import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigDecimal @@ -68,8 +69,8 @@ sealed class AssetTransferValidationFailure { data class AssetTransferPayload( val transfer: WeightedAssetTransfer, - val originFee: BigDecimal, - val crossChainFee: BigDecimal?, + val originFee: DecimalFee, + val crossChainFee: DecimalFee?, val originCommissionAsset: Asset, val originUsedAsset: Asset ) @@ -80,11 +81,11 @@ val AssetTransferPayload.isSendingCommissionAsset val AssetTransferPayload.isReceivingCommissionAsset get() = transfer.destinationChainAsset == transfer.destinationChain.commissionAsset -val AssetTransferPayload.originFeeInUsedAsset: BigDecimal +val AssetTransferPayload.originFeeInUsedAsset: DecimalFee? get() = if (isSendingCommissionAsset) { originFee } else { - BigDecimal.ZERO + null } val AssetTransferPayload.receivingAmountInCommissionAsset: BigInteger diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughAmountToTransferValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughAmountToTransferValidation.kt index d96bbfbb9b..11d334e60a 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughAmountToTransferValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughAmountToTransferValidation.kt @@ -8,6 +8,9 @@ import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.ValidationSystemBuilder import io.novafoundation.nova.feature_wallet_api.R import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.GenericFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.SimpleFee +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeByRequestedAccountOrZero import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigDecimal @@ -17,18 +20,29 @@ interface NotEnoughToPayFeesError { val fee: BigDecimal } -class EnoughAmountToTransferValidation( - private val feeExtractor: AmountProducer

, +typealias EnoughAmountToTransferValidation = EnoughAmountToTransferValidationGeneric + +class EnoughAmountToTransferValidationGeneric( + private val feeExtractor: GenericFeeProducer, private val availableBalanceProducer: AmountProducer

, - private val errorProducer: (P, availableToPayFees: BigDecimal) -> E, + private val errorProducer: (ErrorContext

) -> E, private val skippable: Boolean = false, private val extraAmountExtractor: AmountProducer

= { BigDecimal.ZERO }, ) : Validation { + class ErrorContext

( + + val payload: P, + + val availableToPayFees: BigDecimal, + + val fee: BigDecimal, + ) + companion object; override suspend fun validate(value: P): ValidationStatus { - val fee = feeExtractor(value) + val fee = feeExtractor(value).networkFeeByRequestedAccountOrZero val available = availableBalanceProducer(value) val amount = extraAmountExtractor(value) @@ -39,16 +53,16 @@ class EnoughAmountToTransferValidation( val failureLevel = if (skippable) DefaultFailureLevel.WARNING else DefaultFailureLevel.ERROR - ValidationStatus.NotValid(failureLevel, errorProducer(value, maxUsable)) + ValidationStatus.NotValid(failureLevel, errorProducer(ErrorContext(value, maxUsable, fee))) } } } fun ValidationSystemBuilder.sufficientBalance( - fee: AmountProducer

= { BigDecimal.ZERO }, + fee: FeeProducer

= { null }, amount: AmountProducer

= { BigDecimal.ZERO }, available: AmountProducer

, - error: (P, availableToPayFees: BigDecimal) -> E, + error: (EnoughAmountToTransferValidationGeneric.ErrorContext

) -> E, skippable: Boolean = false ) = validate( EnoughAmountToTransferValidation( @@ -60,6 +74,22 @@ fun ValidationSystemBuilder.sufficientBalance( ) ) +fun ValidationSystemBuilder.sufficientBalanceGeneric( + fee: GenericFeeProducer = { null }, + amount: AmountProducer

= { BigDecimal.ZERO }, + available: AmountProducer

, + error: (EnoughAmountToTransferValidationGeneric.ErrorContext

) -> E, + skippable: Boolean = false +) = validate( + EnoughAmountToTransferValidationGeneric( + feeExtractor = fee, + extraAmountExtractor = amount, + errorProducer = error, + skippable = skippable, + availableBalanceProducer = available + ) +) + fun ResourceManager.notSufficientBalanceToPayFeeErrorMessage() = getString(R.string.common_not_enough_funds_title) to getString(R.string.common_not_enough_funds_message) diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughBalanceToStayAboveEDValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughBalanceToStayAboveEDValidation.kt index d72d859eba..bde43a625f 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughBalanceToStayAboveEDValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/EnoughBalanceToStayAboveEDValidation.kt @@ -6,6 +6,7 @@ import io.novafoundation.nova.common.validation.ValidationSystemBuilder import io.novafoundation.nova.common.validation.validOrError import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDeposit +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeByRequestedAccountOrZero import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigDecimal @@ -16,7 +17,7 @@ interface InsufficientBalanceToStayAboveEDError { class EnoughBalanceToStayAboveEDValidation( private val assetSourceRegistry: AssetSourceRegistry, - private val fee: AmountProducer

, + private val fee: FeeProducer

, private val balance: AmountProducer

, private val chainWithAsset: (P) -> ChainWithAsset, private val error: (P, BigDecimal) -> E @@ -26,7 +27,7 @@ class EnoughBalanceToStayAboveEDValidation( val chain = chainWithAsset(value).chain val asset = chainWithAsset(value).asset val existentialDeposit = assetSourceRegistry.existentialDeposit(chain, asset) - return validOrError(balance(value) - fee(value) >= existentialDeposit) { + return validOrError(balance(value) - fee(value).networkFeeByRequestedAccountOrZero >= existentialDeposit) { error(value, existentialDeposit) } } @@ -35,7 +36,7 @@ class EnoughBalanceToStayAboveEDValidation( class EnoughTotalToStayAboveEDValidationFactory(private val assetSourceRegistry: AssetSourceRegistry) { fun create( - fee: AmountProducer

, + fee: FeeProducer

, balance: AmountProducer

, chainWithAsset: (P) -> ChainWithAsset, error: (P, BigDecimal) -> E @@ -52,7 +53,7 @@ class EnoughTotalToStayAboveEDValidationFactory(private val assetSourceRegistry: context(ValidationSystemBuilder) fun EnoughTotalToStayAboveEDValidationFactory.validate( - fee: AmountProducer

, + fee: FeeProducer

, balance: AmountProducer

, chainWithAsset: (P) -> ChainWithAsset, error: (P, BigDecimal) -> E diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ExistentialDepositValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ExistentialDepositValidation.kt index 95eaeef657..57277f61ff 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ExistentialDepositValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ExistentialDepositValidation.kt @@ -4,13 +4,14 @@ import io.novafoundation.nova.common.validation.Validation import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.ValidationSystemBuilder import io.novafoundation.nova.common.validation.validOrWarning +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeByRequestedAccountOrZero import java.math.BigDecimal typealias ExistentialDepositError = (remainingAmount: BigDecimal, payload: P) -> E class ExistentialDepositValidation( private val countableTowardsEdBalance: AmountProducer

, - private val feeProducer: AmountProducer

, + private val feeProducer: FeeProducer

, private val extraAmountProducer: AmountProducer

, private val errorProducer: ExistentialDepositError, private val existentialDeposit: AmountProducer

@@ -23,7 +24,7 @@ class ExistentialDepositValidation( val fee = feeProducer(value) val extraAmount = extraAmountProducer(value) - val remainingAmount = countableTowardsEd - fee - extraAmount + val remainingAmount = countableTowardsEd - fee.networkFeeByRequestedAccountOrZero - extraAmount return validOrWarning(remainingAmount >= existentialDeposit) { errorProducer(remainingAmount, value) @@ -33,7 +34,7 @@ class ExistentialDepositValidation( fun ValidationSystemBuilder.doNotCrossExistentialDeposit( countableTowardsEdBalance: AmountProducer

, - fee: AmountProducer

= { BigDecimal.ZERO }, + fee: FeeProducer

= { null }, extraAmount: AmountProducer

= { BigDecimal.ZERO }, existentialDeposit: AmountProducer

, error: ExistentialDepositError, diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/FeeChangeValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/FeeChangeValidation.kt index 45ad6ffb1b..ddf3fb4154 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/FeeChangeValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/FeeChangeValidation.kt @@ -14,10 +14,12 @@ import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_wallet_api.R import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount -import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.GenericFee +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.GenericFeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.SimpleFee import io.novafoundation.nova.feature_wallet_api.presentation.model.GenericDecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeByRequestedAccount +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeByRequestedAccountOrZero import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -27,7 +29,7 @@ private val FEE_RATIO_THRESHOLD = 1.5.toBigDecimal() class FeeChangeValidation( private val calculateFee: suspend (P) -> GenericDecimalFee, - private val currentFee: (P) -> BigDecimal, + private val currentFee: GenericFeeProducer, private val chainAsset: (P) -> Chain.Asset, private val error: (FeeChangeDetectedFailure.Payload) -> E ) : Validation { @@ -36,12 +38,15 @@ class FeeChangeValidation( val oldFee = currentFee(value) val newFee = calculateFee(value) - val areFeesSame = oldFee hasTheSaveValueAs newFee.decimalAmount + val oldAmount = oldFee.networkFeeByRequestedAccountOrZero + val newAmount = newFee.networkFeeByRequestedAccount + + val areFeesSame = oldAmount hasTheSaveValueAs newAmount return areFeesSame isTrueOrError { val payload = FeeChangeDetectedFailure.Payload( - needsUserAttention = newFee.decimalAmount / oldFee > FEE_RATIO_THRESHOLD, - oldFee = oldFee, + needsUserAttention = newAmount / oldAmount > FEE_RATIO_THRESHOLD, + oldFee = oldAmount, newFee = newFee, chainAsset = chainAsset(value) ) @@ -65,7 +70,7 @@ interface FeeChangeDetectedFailure { fun ValidationSystemBuilder.checkForSimpleFeeChanges( calculateFee: suspend (P) -> Fee, - currentFee: (P) -> BigDecimal, + currentFee: FeeProducer

, chainAsset: (P) -> Chain.Asset, error: (FeeChangeDetectedFailure.Payload) -> E ) { @@ -79,7 +84,7 @@ fun ValidationSystemBuilder.checkForSimpleFeeChanges( fun ValidationSystemBuilder.checkForFeeChanges( calculateFee: suspend (P) -> F, - currentFee: (P) -> BigDecimal, + currentFee: GenericFeeProducer, chainAsset: (P) -> Chain.Asset, error: (FeeChangeDetectedFailure.Payload) -> E ) = validate( @@ -98,16 +103,16 @@ fun ValidationSystemBuilder.checkForFeeChanges( ) ) -fun CoroutineScope.handleFeeSpikeDetected( - error: FeeChangeDetectedFailure, +fun CoroutineScope.handleFeeSpikeDetected( + error: FeeChangeDetectedFailure, resourceManager: ResourceManager, - feeLoaderMixin: FeeLoaderMixin.Presentation, + feeLoaderMixin: GenericFeeLoaderMixin.Presentation, actions: ValidationFlowActions<*> ): TransformedFailure? = handleFeeSpikeDetected( error = error, resourceManager = resourceManager, actions = actions, - setFee = { feeLoaderMixin.setFee(it.newFee.fee) } + setFee = { feeLoaderMixin.setFee(it.newFee.genericFee) } ) fun CoroutineScope.handleFeeSpikeDetected( @@ -123,7 +128,7 @@ fun CoroutineScope.handleFeeSpikeDetected( val chainAsset = error.payload.chainAsset val oldFee = error.payload.oldFee.formatTokenAmount(chainAsset) - val newFee = error.payload.newFee.decimalAmount.formatTokenAmount(chainAsset) + val newFee = error.payload.newFee.networkFeeByRequestedAccount.formatTokenAmount(chainAsset) return TransformedFailure.Custom( Payload( diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/HasEnoughFreeBalanceValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/HasEnoughFreeBalanceValidation.kt index 9b7bab888c..1e421268b7 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/HasEnoughFreeBalanceValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/HasEnoughFreeBalanceValidation.kt @@ -10,6 +10,7 @@ import io.novafoundation.nova.common.validation.isTrueOrError import io.novafoundation.nova.feature_wallet_api.R import io.novafoundation.nova.feature_wallet_api.domain.model.Asset import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeByRequestedAccountOrZero import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigDecimal @@ -22,13 +23,13 @@ interface NotEnoughFreeBalanceError { class HasEnoughFreeBalanceValidation( private val asset: (P) -> Asset, - private val fee: AmountProducer

, + private val fee: FeeProducer

, private val requestedAmount: AmountProducer

, private val error: HasEnoughFreeBalanceErrorProducer ) : Validation { override suspend fun validate(value: P): ValidationStatus { - val freeBalanceAfterFees = asset(value).free - fee(value) + val freeBalanceAfterFees = asset(value).free - fee(value).networkFeeByRequestedAccountOrZero return (freeBalanceAfterFees >= requestedAmount(value)) isTrueOrError { error(asset(value).token.configuration, freeBalanceAfterFees) @@ -38,7 +39,7 @@ class HasEnoughFreeBalanceValidation( fun ValidationSystemBuilder.hasEnoughFreeBalance( asset: (P) -> Asset, - fee: AmountProducer

, + fee: FeeProducer

, requestedAmount: AmountProducer

, error: HasEnoughFreeBalanceErrorProducer ) { diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/Producers.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/Producers.kt index 720eb2376c..e2cf48675e 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/Producers.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/Producers.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_wallet_api.domain.validation -import io.novafoundation.nova.feature_wallet_api.domain.model.Token +import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.model.GenericDecimalFee import java.math.BigDecimal import java.math.BigInteger @@ -8,4 +9,6 @@ typealias AmountProducer

= suspend (P) -> BigDecimal typealias PlanksProducer

= suspend (P) -> BigInteger -typealias TokenProducer

= suspend (P) -> Token +typealias FeeProducer

= suspend (P) -> DecimalFee? + +typealias GenericFeeProducer = suspend (P) -> GenericDecimalFee? diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt index d894dfb7df..4e261044e2 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeLoaderMixin.kt @@ -2,7 +2,6 @@ package io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee import androidx.lifecycle.LiveData import androidx.lifecycle.asFlow -import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.mixin.api.Retriable import io.novafoundation.nova.common.utils.castOrNull import io.novafoundation.nova.common.utils.inBackground @@ -23,7 +22,6 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.transform -import java.math.BigDecimal sealed class FeeStatus { object Loading : FeeStatus() @@ -75,12 +73,6 @@ interface GenericFeeLoaderMixin : Retriable { suspend fun setFee(fee: F?) fun invalidateFee() - - @Deprecated( - message = "Use `awaitDecimalFee` instead since it holds more information about fee", - replaceWith = ReplaceWith("awaitDecimalFee().networkFeeDecimalAmount") - ) - suspend fun awaitFee(): BigDecimal = awaitDecimalFee().networkFeeDecimalAmount } interface Factory { @@ -94,20 +86,8 @@ interface GenericFeeLoaderMixin : Retriable { interface FeeLoaderMixin : GenericFeeLoaderMixin { - // Additional methods in this interface are only for backward-compatibility to simplify migration of the old code interface Presentation : GenericFeeLoaderMixin.Presentation, FeeLoaderMixin { - @Deprecated("Use setFee(fee: GenericFee)") - suspend fun setFee(feeAmount: BigDecimal?) - - @Deprecated("Use awaitFee()") - fun requireFee( - block: (BigDecimal) -> Unit, - onError: (title: String, message: String) -> Unit, - ) - - suspend fun setFee(fee: Fee?) = setFee(fee?.let(::SimpleFee)) - fun loadFee( coroutineScope: CoroutineScope, expectedChain: ChainId? = null, @@ -149,32 +129,28 @@ fun GenericFeeLoaderMixin.loadedFeeOrNullFlow(): Flow { } } +fun GenericFeeLoaderMixin.loadedDecimalFeeOrNullFlow(): Flow?> { + return feeLiveData.asFlow().map { + it.castOrNull>()?.feeModel?.decimalFee + } +} + fun GenericFeeLoaderMixin.loadedFeeModelOrNullFlow(): Flow?> { return feeLiveData .asFlow() .map { it.castOrNull>()?.feeModel } } -fun GenericFeeLoaderMixin.getFeeOrNull(): F? { +fun GenericFeeLoaderMixin.getDecimalFeeOrNull(): GenericDecimalFee? { return feeLiveData.value .castOrNull>() ?.feeModel ?.decimalFee - ?.genericFee } fun FeeLoaderMixin.Factory.create(assetFlow: Flow) = create(assetFlow.map { it.token }) fun FeeLoaderMixin.Factory.create(tokenUseCase: TokenUseCase) = create(tokenUseCase.currentTokenFlow()) -fun FeeLoaderMixin.Presentation.requireFee( - viewModel: BaseViewModel, - block: (BigDecimal) -> Unit, -) { - requireFee(block) { title, message -> - viewModel.showError(title, message) - } -} - fun FeeLoaderMixin.Presentation.connectWith( inputSource1: Flow, inputSource2: Flow, diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeParcelModel.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeParcelModel.kt index 6e4c5dca62..c3b555b7ce 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeParcelModel.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/FeeParcelModel.kt @@ -1,9 +1,12 @@ package io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee import android.os.Parcelable +import io.novafoundation.nova.feature_account_api.data.extrinsic.SubmissionOrigin import io.novafoundation.nova.feature_account_api.data.model.EvmFee -import io.novafoundation.nova.feature_account_api.data.model.InlineFee +import io.novafoundation.nova.feature_account_api.data.model.SubstrateFee import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee +import io.novafoundation.nova.feature_wallet_api.presentation.model.GenericDecimalFee +import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.android.parcel.Parcelize import java.math.BigDecimal import java.math.BigInteger @@ -11,33 +14,55 @@ import java.math.BigInteger sealed interface FeeParcelModel : Parcelable { val amount: BigDecimal + + val submissionOrigin: SubmissionOriginParcelModel } +@Parcelize +class SubmissionOriginParcelModel( + val requested: AccountId, + val actual: AccountId +) : Parcelable + @Parcelize class EvmFeeParcelModel( val gasLimit: BigInteger, val gasPrice: BigInteger, - override val amount: BigDecimal + override val amount: BigDecimal, + override val submissionOrigin: SubmissionOriginParcelModel ) : FeeParcelModel @Parcelize class SimpleFeeParcelModel( val planks: BigInteger, - override val amount: BigDecimal + override val amount: BigDecimal, + override val submissionOrigin: SubmissionOriginParcelModel ) : FeeParcelModel -fun mapFeeToParcel(decimalFee: DecimalFee): FeeParcelModel { - return when (val fee = decimalFee.fee) { - is EvmFee -> EvmFeeParcelModel(gasLimit = fee.gasLimit, gasPrice = fee.gasPrice, amount = decimalFee.decimalAmount) - else -> SimpleFeeParcelModel(decimalFee.fee.amount, decimalFee.decimalAmount) +fun mapFeeToParcel(decimalFee: GenericDecimalFee<*>): FeeParcelModel { + val submissionOrigin = mapSubmissionOriginToParcel(decimalFee.networkFee.submissionOrigin) + + return when (val fee = decimalFee.networkFee) { + is EvmFee -> EvmFeeParcelModel(gasLimit = fee.gasLimit, gasPrice = fee.gasPrice, amount = decimalFee.networkFeeDecimalAmount, submissionOrigin) + else -> SimpleFeeParcelModel(decimalFee.networkFee.amount, decimalFee.networkFeeDecimalAmount, submissionOrigin) } } +private fun mapSubmissionOriginToParcel(submissionOrigin: SubmissionOrigin): SubmissionOriginParcelModel { + return with(submissionOrigin) { SubmissionOriginParcelModel(requested = requestedOrigin, actual = actualOrigin) } +} + fun mapFeeFromParcel(parcelFee: FeeParcelModel): DecimalFee { + val submissionOrigin = mapSubmissionOriginFromParcel(parcelFee.submissionOrigin) + val fee = when (parcelFee) { - is EvmFeeParcelModel -> EvmFee(gasLimit = parcelFee.gasLimit, gasPrice = parcelFee.gasPrice) - is SimpleFeeParcelModel -> InlineFee(parcelFee.planks) + is EvmFeeParcelModel -> EvmFee(gasLimit = parcelFee.gasLimit, gasPrice = parcelFee.gasPrice, submissionOrigin) + is SimpleFeeParcelModel -> SubstrateFee(parcelFee.planks, submissionOrigin) } return DecimalFee(SimpleFee(fee), parcelFee.amount) } + +private fun mapSubmissionOriginFromParcel(submissionOrigin: SubmissionOriginParcelModel): SubmissionOrigin { + return with(submissionOrigin) { SubmissionOrigin(requestedOrigin = requested, actualOrigin = actual) } +} diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/GenericFeeLoaderProvider.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/GenericFeeLoaderProvider.kt index 5a6f8fdb51..b9dfcd38a3 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/GenericFeeLoaderProvider.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/mixin/fee/GenericFeeLoaderProvider.kt @@ -5,11 +5,9 @@ import io.novafoundation.nova.common.mixin.api.RetryPayload import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.firstNotNull -import io.novafoundation.nova.feature_account_api.data.model.InlineFee import io.novafoundation.nova.feature_wallet_api.R import io.novafoundation.nova.feature_wallet_api.data.mappers.mapFeeToFeeModel import io.novafoundation.nova.feature_wallet_api.domain.model.Token -import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope @@ -19,7 +17,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.math.BigDecimal class FeeLoaderProviderFactory( private val resourceManager: ResourceManager, @@ -44,33 +41,7 @@ private class FeeLoaderProvider( resourceManager: ResourceManager, configuration: GenericFeeLoaderMixin.Configuration, tokenFlow: Flow, -) : GenericFeeLoaderProvider(resourceManager, configuration, tokenFlow), FeeLoaderMixin.Presentation { - - override suspend fun setFee(feeAmount: BigDecimal?) { - val fee = feeAmount?.let { - val token = tokenFlow.firstNotNull() - InlineFee(token.planksFromAmount(feeAmount)) - } - - setFee(fee) - } - - override fun requireFee( - block: (BigDecimal) -> Unit, - onError: (title: String, message: String) -> Unit, - ) { - val feeStatus = feeLiveData.value - - if (feeStatus is FeeStatus.Loaded) { - block(feeStatus.feeModel.decimalFee.decimalAmount) - } else { - onError( - resourceManager.getString(R.string.fee_not_yet_loaded_title), - resourceManager.getString(R.string.fee_not_yet_loaded_message) - ) - } - } -} +) : GenericFeeLoaderProvider(resourceManager, configuration, tokenFlow), FeeLoaderMixin.Presentation private open class GenericFeeLoaderProvider( protected val resourceManager: ResourceManager, diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/model/FeeModel.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/model/FeeModel.kt index 24e274ee46..204780318c 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/model/FeeModel.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/presentation/model/FeeModel.kt @@ -1,12 +1,13 @@ package io.novafoundation.nova.feature_wallet_api.presentation.model +import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.data.model.requestedAccountPaysFees import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.GenericFee import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.SimpleFee import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigDecimal -typealias FeeModel = GenericFeeModel typealias DecimalFee = GenericDecimalFee class GenericFeeModel( @@ -20,8 +21,14 @@ class GenericDecimalFee( val networkFeeDecimalAmount: BigDecimal ) { - @Deprecated("This field has unclear semantics in a case of custom fee structure", replaceWith = ReplaceWith("networkFeeDecimalAmount")) - val decimalAmount: BigDecimal = networkFeeDecimalAmount - - val fee: Fee = genericFee.networkFee + val networkFee: Fee = genericFee.networkFee } + +val GenericDecimalFee.networkFeeByRequestedAccount: BigDecimal + get() = if (networkFee.requestedAccountPaysFees) networkFeeDecimalAmount else BigDecimal.ZERO + +val GenericDecimalFee?.networkFeeByRequestedAccountOrZero: BigDecimal + get() = this?.networkFeeByRequestedAccount.orZero() + +val GenericDecimalFee?.networkFeeDecimalAmount: BigDecimal + get() = this?.networkFeeDecimalAmount.orZero() diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/BaseAssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/BaseAssetTransfers.kt index a0b9628eb9..df4967b931 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/BaseAssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/BaseAssetTransfers.kt @@ -90,7 +90,7 @@ abstract class BaseAssetTransfers( recipientCanAcceptTransfer(assetSourceRegistry) } - protected fun AssetTransfersValidationSystemBuilder.doNotCrossExistentialDeposit() = doNotCrossExistentialDeposit( + private fun AssetTransfersValidationSystemBuilder.doNotCrossExistentialDeposit() = doNotCrossExistentialDeposit( assetSourceRegistry = assetSourceRegistry, fee = { it.originFeeInUsedAsset }, extraAmount = { it.transfer.amount }, diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmErc20/EvmErc20AssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmErc20/EvmErc20AssetTransfers.kt index e0ac0dfaee..bc6dbe28c6 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmErc20/EvmErc20AssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmErc20/EvmErc20AssetTransfers.kt @@ -56,7 +56,7 @@ class EvmErc20AssetTransfers( override suspend fun performTransfer(transfer: WeightedAssetTransfer): Result { return evmTransactionService.transact( chainId = transfer.originChain.id, - presetFee = transfer.decimalFee.fee, + presetFee = transfer.decimalFee.networkFee, fallbackGasLimit = ERC_20_UPPER_GAS_LIMIT ) { transfer(transfer) diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmNative/EvmNativeAssetTransfers.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmNative/EvmNativeAssetTransfers.kt index ce7ab5f36b..33d82dc9aa 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmNative/EvmNativeAssetTransfers.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/evmNative/EvmNativeAssetTransfers.kt @@ -53,7 +53,7 @@ class EvmNativeAssetTransfers( return evmTransactionService.transact( chainId = transfer.originChain.id, fallbackGasLimit = NATIVE_COIN_TRANSFER_GAS_LIMIT, - presetFee = transfer.decimalFee.fee, + presetFee = transfer.decimalFee.networkFee, ) { nativeTransfer(transfer) } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/validations/Common.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/validations/Common.kt index cc7ae9474b..674c352673 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/validations/Common.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/transfers/validations/Common.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.t import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED import io.novafoundation.nova.feature_wallet_api.domain.validation.AmountProducer import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory +import io.novafoundation.nova.feature_wallet_api.domain.validation.FeeProducer import io.novafoundation.nova.feature_wallet_api.domain.validation.PhishingValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.checkForSimpleFeeChanges import io.novafoundation.nova.feature_wallet_api.domain.validation.doNotCrossExistentialDeposit @@ -72,7 +73,7 @@ fun AssetTransfersValidationSystemBuilder.checkForFeeChanges( fun AssetTransfersValidationSystemBuilder.doNotCrossExistentialDeposit( assetSourceRegistry: AssetSourceRegistry, - fee: AmountProducer, + fee: FeeProducer, extraAmount: AmountProducer, ) = doNotCrossExistentialDeposit( countableTowardsEdBalance = { it.originUsedAsset.balanceCountedTowardsED() }, @@ -86,11 +87,11 @@ fun AssetTransfersValidationSystemBuilder.sufficientTransferableBalanceToPayOrig available = { it.originCommissionAsset.transferable }, amount = { it.sendingAmountInCommissionAsset }, fee = { it.originFee }, - error = { payload, availableToPayFees -> + error = { context -> AssetTransferValidationFailure.NotEnoughFunds.InCommissionAsset( - chainAsset = payload.transfer.originChain.commissionAsset, - fee = payload.originFee, - maxUsable = availableToPayFees + chainAsset = context.payload.transfer.originChain.commissionAsset, + fee = context.fee, + maxUsable = context.availableToPayFees ) } ) @@ -98,8 +99,8 @@ fun AssetTransfersValidationSystemBuilder.sufficientTransferableBalanceToPayOrig fun AssetTransfersValidationSystemBuilder.sufficientBalanceInUsedAsset() = sufficientBalance( available = { it.originUsedAsset.transferable }, amount = { it.transfer.amount }, - fee = { BigDecimal.ZERO }, - error = { _, _ -> AssetTransferValidationFailure.NotEnoughFunds.InUsedAsset } + fee = { null }, + error = { AssetTransferValidationFailure.NotEnoughFunds.InUsedAsset } ) fun AssetTransfersValidationSystemBuilder.recipientIsNotSystemAccount() = notSystemAccount( diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainTransactor.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainTransactor.kt index e83fa91991..34e5f0b643 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainTransactor.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/RealCrossChainTransactor.kt @@ -1,7 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.crosschain import io.novafoundation.nova.common.data.network.runtime.binding.Weight -import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.common.utils.xTokensName import io.novafoundation.nova.common.utils.xcmPalletName import io.novafoundation.nova.common.validation.ValidationSystem @@ -23,6 +22,7 @@ import io.novafoundation.nova.feature_wallet_api.domain.model.XcmTransferType import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory import io.novafoundation.nova.feature_wallet_api.domain.validation.PhishingValidationFactory +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeByRequestedAccountOrZero import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.transfers.validations.doNotCrossExistentialDeposit import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.transfers.validations.notDeadRecipientInCommissionAsset import io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.transfers.validations.notDeadRecipientInUsedAsset @@ -65,7 +65,7 @@ class RealCrossChainTransactor( doNotCrossExistentialDeposit( assetSourceRegistry = assetSourceRegistry, fee = { it.originFeeInUsedAsset }, - extraAmount = { it.transfer.amount + it.crossChainFee.orZero() } + extraAmount = { it.transfer.amount + it.crossChainFee.networkFeeByRequestedAccountOrZero } ) } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/validations/CrossChainFeeValidation.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/validations/CrossChainFeeValidation.kt index edb71c5dbd..f3f21e1f85 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/validations/CrossChainFeeValidation.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/crosschain/validations/CrossChainFeeValidation.kt @@ -1,6 +1,5 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.crosschain.validations -import io.novafoundation.nova.common.utils.orZero import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.common.validation.isTrueOrError import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransferPayload @@ -8,13 +7,15 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.t import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfersValidation import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.AssetTransfersValidationSystemBuilder import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.tranfers.originFeeInUsedAsset +import io.novafoundation.nova.feature_wallet_api.presentation.model.networkFeeByRequestedAccountOrZero class CrossChainFeeValidation : AssetTransfersValidation { override suspend fun validate(value: AssetTransferPayload): ValidationStatus { - val remainingBalanceAfterTransfer = value.originUsedAsset.transferable - value.transfer.amount - value.originFeeInUsedAsset + val networkFeeInUsedAsset = value.originFeeInUsedAsset.networkFeeByRequestedAccountOrZero + val remainingBalanceAfterTransfer = value.originUsedAsset.transferable - value.transfer.amount - networkFeeInUsedAsset - val crossChainFee = value.crossChainFee.orZero() + val crossChainFee = value.crossChainFee.networkFeeByRequestedAccountOrZero val remainsEnoughToPayCrossChainFees = remainingBalanceAfterTransfer >= crossChainFee return remainsEnoughToPayCrossChainFees isTrueOrError { diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/NovaSigner.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/NovaSigner.kt index 1b05ab4c5f..23d04326ca 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/NovaSigner.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/extrinsic/signer/NovaSigner.kt @@ -8,3 +8,19 @@ interface NovaSigner : Signer { suspend fun signerAccountId(chain: Chain): AccountId } + +interface FeeSigner : NovaSigner { + + /** + * In contrast with [signerAccountId] which for [FeeSigner] is supposed to return an account id derived from a fake keypair, + * This method returns a real account id that later will sign the transaction we're calculating fee + * This is useful for the client code to understand which account which actually pay the fee since it might differ from the requested account id + */ + suspend fun actualFeeSignerId(chain: Chain): AccountId + + /** + * Similar to [actualFeeSignerId] but returns accountId that was specified as the transaction origin + * It might not be equal to [actualFeeSignerId] if [Signer] modifies the payload + */ + suspend fun requestedFeeSignerId(chain: Chain): AccountId +} From 862b94ea5ad2ff8220c68506d2ebb3ce8ba11ea8 Mon Sep 17 00:00:00 2001 From: Valentun Date: Thu, 28 Dec 2023 11:21:57 +0300 Subject: [PATCH 074/100] Fix conflicts --- .github/workflows/android_build.yml | 5 +- .../nova/NftUniquesIntegrationTest.kt | 105 ++++++++++-------- .../nova/balances/BalancesIntegrationTest.kt | 6 +- .../send/amount/SelectSendViewModel.kt | 2 + 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/.github/workflows/android_build.yml b/.github/workflows/android_build.yml index bb807afa61..9f99797ba2 100644 --- a/.github/workflows/android_build.yml +++ b/.github/workflows/android_build.yml @@ -44,6 +44,8 @@ on: required: true INFURA_API_KEY: required: true + DWELLIR_API_KEY: + required: true WALLET_CONNECT_PROJECT_ID: required: true # Special secrets for signing: @@ -81,6 +83,7 @@ env: EHTERSCAN_API_KEY_MOONRIVER: ${{ secrets.EHTERSCAN_API_KEY_MOONRIVER }} EHTERSCAN_API_KEY_ETHEREUM: ${{ secrets.EHTERSCAN_API_KEY_ETHEREUM }} INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }} + DWELLIR_API_KEY: ${{ secrets.DWELLIR_API_KEY }} WALLET_CONNECT_PROJECT_ID: ${{ secrets.WALLET_CONNECT_PROJECT_ID }} CI_MARKET_KEYSTORE_PASS: ${{ secrets.CI_MARKET_KEYSTORE_PASS }} @@ -123,7 +126,7 @@ jobs: fileName: ${{ inputs.keystore-file-name }} fileDir: './app/' encodedString: ${{ env.CI_GITHUB_KEYSTORE_KEY_FILE }} - + - name: 🔐 Getting market sign key if: ${{ startsWith(inputs.keystore-file-name, 'market_key.jks') }} uses: timheuer/base64-to-file@v1.1 diff --git a/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt index 59c3bff422..dfa7a1fd3f 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/NftUniquesIntegrationTest.kt @@ -15,6 +15,8 @@ import io.novafoundation.nova.runtime.di.RuntimeApi import io.novafoundation.nova.runtime.di.RuntimeComponent import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.multiNetwork.connection.ChainConnection +import io.novafoundation.nova.runtime.storage.source.multi.MultiQueryBuilder +import io.novafoundation.nova.runtime.storage.source.query.multi import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.fearless_utils.runtime.metadata.storage @@ -88,60 +90,71 @@ class NftUniquesIntegrationTest { val classesIds = classesWithInstances.map { (collection, _) -> collection }.distinct() - val classMetadataStorage = runtime.metadata.uniques().storage("ClassMetadataOf") - val classStorage = runtime.metadata.uniques().storage("Class") - val instanceMetadataStorage = runtime.metadata.uniques().storage("InstanceMetadataOf") - val instanceDetailsStorage = runtime.metadata.uniques().storage("Asset") - - val multiQueryResults = multiInternal { - classMetadataStorage.querySingleArgKeys(classesIds) - classStorage.querySingleArgKeys(classesIds) - instanceMetadataStorage.queryKeys(classesWithInstances) - instanceDetailsStorage.queryKeys(classesWithInstances) - } + val classDetailsDescriptor: MultiQueryBuilder.Descriptor + val classMetadatasDescriptor: MultiQueryBuilder.Descriptor + val instancesDetailsDescriptor: MultiQueryBuilder.Descriptor, UniquesInstance.Details> + val instancesMetadataDescriptor: MultiQueryBuilder.Descriptor, UniquesInstance.Metadata?> + + val multiQueryResults = multi { + classDetailsDescriptor = runtime.metadata.uniques().storage("Class").querySingleArgKeys( + keysArgs = classesIds, + keyExtractor = { it.component1() }, + binding = { parsedValue -> + val classDetailsStruct = parsedValue.cast() + + UniquesClass.Details( + instances = classDetailsStruct.getTyped("instances"), + frozen = classDetailsStruct.getTyped("isFrozen") + ) + } + ) - val classDetails = multiQueryResults.getValue(classStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() } - .mapValues { (_, parsedValue) -> - val classDetailsStruct = parsedValue.cast() + classMetadatasDescriptor = runtime.metadata.uniques().storage("ClassMetadataOf").querySingleArgKeys( + keysArgs = classesIds, + keyExtractor = { it.component1() }, + binding = { parsedValue -> + parsedValue?.cast()?.let { classMetadataStruct -> + UniquesClass.Metadata( + deposit = classMetadataStruct.getTyped("deposit"), + data = bindString(classMetadataStruct["data"]) + ) + } + } + ) - UniquesClass.Details( - instances = classDetailsStruct.getTyped("instances"), - frozen = classDetailsStruct.getTyped("isFrozen") - ) - } + instancesDetailsDescriptor = runtime.metadata.uniques().storage("Asset").queryKeys( + keysArgs = classesWithInstances, + keyExtractor = { it.component1() to it.component2() }, + binding = { parsedValue -> + val instanceDetailsStruct = parsedValue.cast() - val classMetadatas = multiQueryResults.getValue(classMetadataStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() } - .mapValues { (_, parsedValue) -> - parsedValue?.cast()?.let { classMetadataStruct -> - UniquesClass.Metadata( - deposit = classMetadataStruct.getTyped("deposit"), - data = bindString(classMetadataStruct["data"]) + UniquesInstance.Details( + owner = chain.addressOf(bindAccountId(instanceDetailsStruct["owner"])), + frozen = bindBoolean(instanceDetailsStruct["isFrozen"]) ) } - } + ) - val instancesDetails = multiQueryResults.getValue(instanceDetailsStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() to keyComponents.component2() } - .mapValues { (_, parsedValue) -> - val instanceDetailsStruct = parsedValue.cast() + instancesMetadataDescriptor = runtime.metadata.uniques().storage("InstanceMetadataOf").queryKeys( + keysArgs = classesWithInstances, + keyExtractor = { it.component1() to it.component2() }, + binding = { parsedValue -> + parsedValue?.cast()?.let { + UniquesInstance.Metadata( + data = bindString(it["data"]) + ) + } + } + ) + } - UniquesInstance.Details( - owner = chain.addressOf(bindAccountId(instanceDetailsStruct["owner"])), - frozen = bindBoolean(instanceDetailsStruct["isFrozen"]) - ) - } + val classDetails = multiQueryResults[classDetailsDescriptor] - val instancesMetadatas = multiQueryResults.getValue(instanceMetadataStorage) - .mapKeys { (keyComponents, _) -> keyComponents.component1() to keyComponents.component2() } - .mapValues { (_, parsedValue) -> - parsedValue?.cast()?.let { - UniquesInstance.Metadata( - data = bindString(it["data"]) - ) - } - } + val classMetadatas = multiQueryResults[classMetadatasDescriptor] + + val instancesDetails = multiQueryResults[instancesDetailsDescriptor] + + val instancesMetadatas = multiQueryResults[instancesMetadataDescriptor] val classes = classesIds.associateWith { classId -> UniquesClass( diff --git a/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt index 08037c0e77..7b1bebb9ab 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt @@ -49,11 +49,9 @@ class BalancesIntegrationTest( companion object { @JvmStatic @Parameterized.Parameters(name = "{1}") - fun data(): ArrayList> { + fun data(): List> { val arrayOfNetworks: Array = Gson().fromJson(URL(TEST_CHAINS_URL).readText()) - val listNetworks: ArrayList> = ArrayList() - arrayOfNetworks.forEach { listNetworks.add(arrayOf(it.chainId, it.name, it.account)) } - return listNetworks + return arrayOfNetworks.map { arrayOf(it.chainId, it.name, it.account) } } class TestData( diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt index c6ac601ca8..70e2e91241 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendViewModel.kt @@ -59,6 +59,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal @@ -113,6 +114,7 @@ class SelectSendViewModel( } private val availableCrossChainDestinations = availableCrossChainDestinations() + .onStart { emit(emptyList()) } .shareInBackground() val isSelectAddressAvailable = combine(originChain, destinationChain) { originChain, destinationChain -> From cd5dd6340c798883ccf611fc2c3152c9d0882ef8 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Thu, 28 Dec 2023 21:16:54 +0300 Subject: [PATCH 075/100] per chain proxy sync (#1303) --- .../nova/core_db/dao/MetaAccountDao.kt | 12 +-- .../data/proxy/RealProxySyncService.kt | 95 ++++++++----------- 2 files changed, 48 insertions(+), 59 deletions(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 576c933541..7f982caeac 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -99,17 +99,17 @@ interface MetaAccountDao { suspend fun insertChainAccounts(chainAccounts: List) @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertProxy(proxyLocal: ProxyAccountLocal) + suspend fun insertProxy(proxyLocal: ProxyAccountLocal) @Query("SELECT * FROM meta_accounts") - fun getMetaAccounts(): List + suspend fun getMetaAccounts(): List @Query("SELECT * FROM meta_accounts") @Transaction suspend fun getJoinedMetaAccountsInfo(): List - @Query("SELECT * FROM meta_accounts WHERE status = :status") - fun getMetaAccountsByStatus(status: MetaAccountLocal.Status): List + @Query("SELECT id FROM meta_accounts WHERE status = :status") + suspend fun getMetaAccountIdsByStatus(status: MetaAccountLocal.Status): List @Query("SELECT * FROM meta_accounts") suspend fun getMetaAccountsInfo(): List @@ -126,8 +126,8 @@ interface MetaAccountDao { @Query(META_ACCOUNT_WITH_BALANCE_QUERY) fun metaAccountWithBalanceFlow(metaId: Long): Flow> - @Query("SELECT * FROM proxy_accounts") - suspend fun getAllProxyAccounts(): List + @Query("SELECT * FROM proxy_accounts WHERE chainId = :chainId") + suspend fun getProxyAccounts(chainId: String): List @Query("UPDATE meta_accounts SET isSelected = (id = :metaId)") suspend fun selectMetaAccount(metaId: Long) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index d3beeeb557..1bec2fd494 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -21,21 +21,19 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountId import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository -import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository.Payload import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.findChains import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -import kotlinx.coroutines.withTimeoutOrNull private const val SYNC_TIMEOUT = 10_000L // 10 seconds class RealProxySyncService( private val chainRegistry: ChainRegistry, private val proxyRepository: ProxyRepository, - private val accounRepository: AccountRepository, + private val accountRepository: AccountRepository, private val accountDao: MetaAccountDao, private val identityProvider: IdentityProvider, private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, @@ -49,75 +47,69 @@ class RealProxySyncService( } } - private suspend fun startSyncInternal() { + private suspend fun startSyncInternal() = runCatching { val metaAccounts = getMetaAccounts() - if (metaAccounts.isEmpty()) return + if (metaAccounts.isEmpty()) return@runCatching - runCatching { - val supportedProxyChains = getSupportedProxyChains() + val supportedProxyChains = getSupportedProxyChains() - val chainsToAccountIds = supportedProxyChains.associateWith { chain -> chain.getAvailableAccountIds(metaAccounts) } + supportedProxyChains.forEach { chain -> + syncChainProxies(chain, metaAccounts) + } + }.onFailure { + Log.e(LOG_TAG, "Failed to sync proxy delegators", it) + } - val proxiedsWithProxies = chainsToAccountIds.flatMap { (chain, accountIds) -> - withTimeoutOrNull(SYNC_TIMEOUT) { - proxyRepository.getAllProxiesForMetaAccounts(chain.id, accountIds) - }.orEmpty() - } + private suspend fun syncChainProxies(chain: Chain, metaAccounts: List) = runCatching { + Log.d(LOG_TAG, "Started syncing proxies for ${chain.name}") - val oldProxies = accountDao.getAllProxyAccounts() + val availableAccountIds = chain.getAvailableAccountIds(metaAccounts) - val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) + val proxiedsWithProxies = proxyRepository.getAllProxiesForMetaAccounts(chain.id, availableAccountIds) - val identitiesByChain = notAddedProxies.loadProxiedIdentities() - val addedProxiedsIds = notAddedProxies.map { - val identity = identitiesByChain[it.proxied.chainId]?.get(it.proxied.accountId.intoKey()) + val oldProxies = accountDao.getProxyAccounts(chain.id) - val proxiedMetaId = proxiedAddAccountRepository.addAccount(Payload(it, identity)) + val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) - it to proxiedMetaId - } + val identitiesByChain = notAddedProxies.loadProxiedIdentities(chain.id) + val addedProxiedsMetaIds = notAddedProxies.map { + val identity = identitiesByChain[it.proxied.accountId.intoKey()] - val deactivatedMetaAccountIds = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) - accountDao.changeAccountsStatus(deactivatedMetaAccountIds, MetaAccountLocal.Status.DEACTIVATED) - - val changedMetaIds = addedProxiedsIds.map { it.second } + deactivatedMetaAccountIds - metaAccountsUpdatesRegistry.addMetaIds(changedMetaIds) - }.onFailure { - Log.e(LOG_TAG, "Failed to sync proxy delegators", it) + proxiedAddAccountRepository.addAccount(ProxiedAddAccountRepository.Payload(it, identity)) } + + val deactivatedMetaAccountIds = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) + accountDao.changeAccountsStatus(deactivatedMetaAccountIds, MetaAccountLocal.Status.DEACTIVATED) + + val changedMetaIds = addedProxiedsMetaIds + deactivatedMetaAccountIds + metaAccountsUpdatesRegistry.addMetaIds(changedMetaIds) + }.onFailure { + Log.e(LOG_TAG, "Failed to sync proxy delegators in chain ${chain.name}", it) + }.onSuccess { + Log.d(LOG_TAG, "Finished syncing proxies for ${chain.name}") } - private suspend fun filterNotAddedProxieds( + private fun filterNotAddedProxieds( proxiedsWithProxies: List, oldProxies: List ): List { - val oldInditifiers = oldProxies.map { it.identifier }.toSet() - return proxiedsWithProxies.filter { it.toLocalIdentifier() !in oldInditifiers } + val oldIdentifiers = oldProxies.mapToSet { it.identifier } + return proxiedsWithProxies.filter { it.toLocalIdentifier() !in oldIdentifiers } } private suspend fun getDeactivatedMetaIds( onChainProxies: List, oldProxies: List ): List { - val newIdentifiers = onChainProxies.map { it.toLocalIdentifier() }.toSet() + val newIdentifiers = onChainProxies.mapToSet { it.toLocalIdentifier() } val accountsToDeactivate = oldProxies.filter { it.identifier !in newIdentifiers } .map { it.proxiedMetaId } return accountsToDeactivate.takeNotYetDeactivatedMetaAccounts() } - private suspend fun getPedsToRemove( - oldProxies: List, - proxiedsMetaAccounts: List - ): List { - val proxiedsMetaIds = proxiedsMetaAccounts.mapToSet { it.id } - - return oldProxies.filter { it.proxiedMetaId !in proxiedsMetaIds } - .map { it.proxiedMetaId } - } - private suspend fun getMetaAccounts(): List { - return accounRepository.allMetaAccounts() + return accountRepository.allMetaAccounts() .filter { it.isAllowedToSyncProxy() } } @@ -137,7 +129,7 @@ class RealProxySyncService( return chainRegistry.findChains { it.supportProxy } } - private suspend fun Chain.getAvailableAccountIds(metaAccounts: List): List { + private fun Chain.getAvailableAccountIds(metaAccounts: List): List { return metaAccounts.mapNotNull { metaAccount -> val accountId = metaAccount.accountIdIn(chain = this) accountId?.let { @@ -146,19 +138,16 @@ class RealProxySyncService( } } - private suspend fun List.loadProxiedIdentities(): Map> { - return this.groupBy { it.proxied.chainId } - .mapValues { (chainId, proxiedWithProxies) -> - val proxiedAccountIds = proxiedWithProxies.map { it.proxied.accountId } - identityProvider.identitiesFor(proxiedAccountIds, chainId) - } + private suspend fun List.loadProxiedIdentities(chainId: ChainId): Map { + val proxiedAccountIds = map { it.proxied.accountId } + + return identityProvider.identitiesFor(proxiedAccountIds, chainId) } private suspend fun List.takeNotYetDeactivatedMetaAccounts(): List { - val alreadyDeactivatedMetaAccountIds = accountDao.getMetaAccountsByStatus(MetaAccountLocal.Status.DEACTIVATED) - .mapToSet { it.id } + val alreadyDeactivatedMetaAccountIds = accountDao.getMetaAccountIdsByStatus(MetaAccountLocal.Status.DEACTIVATED) - return this - alreadyDeactivatedMetaAccountIds + return this - alreadyDeactivatedMetaAccountIds.toSet() } private fun ProxiedWithProxy.toLocalIdentifier(): String { From 30850086bc5e726f3514df03ec34b702993a24d7 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Thu, 28 Dec 2023 21:17:12 +0300 Subject: [PATCH 076/100] Fix/setup staking state transitions (#1302) * Fix bugs in start staking state transitions * Code style --- .../filters/NotOverSubscribedFilter.kt | 9 +++- .../common/SetupStakingSharedState.kt | 38 ++++++++++----- .../types/DirectConfirmMultiStakingType.kt | 3 +- .../SetupStakingTypeFlowExecutor.kt | 5 +- .../validators/change/StateTransitions.kt | 46 +++++++++++++++---- .../ConfirmChangeValidatorsViewModel.kt | 7 +-- .../ConfirmNominationsViewModel.kt | 2 +- .../review/ReviewCustomValidatorsViewModel.kt | 2 +- .../SetupStakingReviewValidatorsFlowAction.kt | 6 ++- .../search/SearchCustomValidatorsViewModel.kt | 2 +- .../select/SelectCustomValidatorsViewModel.kt | 8 ++-- .../RecommendedValidatorsViewModel.kt | 13 +----- .../start/StartChangeValidatorsViewModel.kt | 41 ++++++++--------- .../current/CurrentValidatorsViewModel.kt | 14 ++---- 14 files changed, 117 insertions(+), 79 deletions(-) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/filters/NotOverSubscribedFilter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/filters/NotOverSubscribedFilter.kt index 8121cf76c2..0011914783 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/filters/NotOverSubscribedFilter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/recommendations/settings/filters/NotOverSubscribedFilter.kt @@ -6,7 +6,12 @@ import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settin object NotOverSubscribedFilter : RecommendationFilter { override fun shouldInclude(model: Validator): Boolean { - // inactive validators are considered as non-oversubscribed - return model.electedInfo?.isOversubscribed ?: true + val isOversubscribed = model.electedInfo?.isOversubscribed + + return if (isOversubscribed != null) { + !isOversubscribed + } else { + true // inactive validators are considered as non-oversubscribed + } } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/SetupStakingSharedState.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/SetupStakingSharedState.kt index e0b1dea75c..cf29bb33c5 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/SetupStakingSharedState.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/common/SetupStakingSharedState.kt @@ -11,17 +11,33 @@ sealed class SetupStakingProcess { fun next( activeStake: Balance, - validators: List, - selectionMethod: ReadyToSubmit.SelectionMethod - ): SetupStakingProcess { - return ReadyToSubmit(activeStake, validators, selectionMethod) + currentlyActiveValidators: List, + ): ChoosingValidators { + return ChoosingValidators(currentlySelectedValidators = currentlyActiveValidators, activeStake) } } - class ReadyToSubmit( + class ChoosingValidators( + val currentlySelectedValidators: List, val activeStake: Balance, - val validators: List, - val selectionMethod: SelectionMethod + ) : SetupStakingProcess() { + + fun next( + newValidators: List, + selectionMethod: ReadyToSubmit.SelectionMethod, + ) = ReadyToSubmit( + activeStake = activeStake, + newValidators = newValidators, + selectionMethod = selectionMethod, + currentlySelectedValidators = currentlySelectedValidators + ) + } + + data class ReadyToSubmit( + val activeStake: Balance, + val newValidators: List, + val selectionMethod: SelectionMethod, + val currentlySelectedValidators: List, ) : SetupStakingProcess() { enum class SelectionMethod { @@ -31,13 +47,11 @@ sealed class SetupStakingProcess { fun changeValidators( newValidators: List, selectionMethod: SelectionMethod - ) = ReadyToSubmit(activeStake, newValidators, selectionMethod) + ) = copy(newValidators = newValidators, selectionMethod = selectionMethod) - fun previous(): Initial { - return Initial + fun previous(): ChoosingValidators { + return ChoosingValidators(currentlySelectedValidators, activeStake) } - - fun reset() = Initial } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/types/DirectConfirmMultiStakingType.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/types/DirectConfirmMultiStakingType.kt index a2c833da62..fb94ce9e7d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/types/DirectConfirmMultiStakingType.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/confirm/types/DirectConfirmMultiStakingType.kt @@ -33,9 +33,10 @@ class DirectConfirmMultiStakingType( override suspend fun onStakingTypeDetailsClicked() { // act as an adapter between new flow and legacy logic val reviewValidatorsState = ReadyToSubmit( - validators = selection.validators, + newValidators = selection.validators, selectionMethod = ReadyToSubmit.SelectionMethod.RECOMMENDED, activeStake = selection.stake, + currentlySelectedValidators = emptyList() ) setupStakingSharedState.set(reviewValidatorsState) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeFlowExecutor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeFlowExecutor.kt index 46e88457fc..b6e4885415 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeFlowExecutor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/start/setupStakingType/SetupStakingTypeFlowExecutor.kt @@ -55,8 +55,9 @@ class SetupDirectStakingFlowExecutor( setupStakingSharedState.set( SetupStakingProcess.ReadyToSubmit( activeStake = selectionStore.getCurrentSelection()?.selection?.stake.orZero(), - validators = selectionStore.getValidatorsOrEmpty(), - selectionMethod = SetupStakingProcess.ReadyToSubmit.SelectionMethod.CUSTOM + newValidators = selectionStore.getValidatorsOrEmpty(), + selectionMethod = SetupStakingProcess.ReadyToSubmit.SelectionMethod.CUSTOM, + currentlySelectedValidators = emptyList() ) ) router.openSelectCustomValidators() diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/StateTransitions.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/StateTransitions.kt index 8b6ae434b2..67d008106b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/StateTransitions.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/StateTransitions.kt @@ -7,9 +7,14 @@ import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStak import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance fun SetupStakingSharedState.reset() = mutate { - when (it) { - is SetupStakingProcess.ReadyToSubmit -> it.reset() - else -> throw IllegalArgumentException("Cannot retract validators from $it state") + SetupStakingProcess.Initial +} + +fun SetupStakingSharedState.retractRecommended() = mutate { + if (it is SetupStakingProcess.ReadyToSubmit && it.selectionMethod == SelectionMethod.RECOMMENDED) { + it.previous() + } else { + it } } @@ -24,23 +29,48 @@ fun SetupStakingSharedState.setRecommendedValidators( fun SetupStakingSharedState.activeStake(): Balance { return when (val state = setupStakingProcess.value) { is SetupStakingProcess.ReadyToSubmit -> state.activeStake - else -> throw IllegalArgumentException("Cannot get active stake from $state state") + is SetupStakingProcess.ChoosingValidators -> state.activeStake + SetupStakingProcess.Initial -> throw IllegalArgumentException("Cannot get active stake from $state state") } } -fun SetupStakingSharedState.getSelectedValidators(): List { +/** + * Validators that has been selected by user during current flow + * Does not count currently selected validators + */ +fun SetupStakingSharedState.getNewValidators(): List { return when (val process = setupStakingProcess.value) { - is SetupStakingProcess.ReadyToSubmit -> process.validators - else -> throw IllegalArgumentException("Cannot get validators from $process state") + is SetupStakingProcess.ReadyToSubmit -> process.newValidators + SetupStakingProcess.Initial, is SetupStakingProcess.ChoosingValidators -> { + throw IllegalArgumentException("Cannot get validators from $process state") + } + } +} + +/** + * Validators that should be shown for the user as selected + * It is either its already updated selection during current flow + * or its currently selected validators (based on on-chain nominations) + */ +fun SetupStakingProcess.getSelectedValidatorsOrNull(): List? { + return when (this) { + is SetupStakingProcess.ReadyToSubmit -> newValidators + is SetupStakingProcess.ChoosingValidators -> currentlySelectedValidators + SetupStakingProcess.Initial -> null } } +fun SetupStakingProcess.getSelectedValidatorsOrEmpty(): List { + return getSelectedValidatorsOrNull().orEmpty() +} + private fun SetupStakingSharedState.setValidators( validators: List, selectionMethod: SelectionMethod ) = mutate { when (it) { is SetupStakingProcess.ReadyToSubmit -> it.changeValidators(validators, selectionMethod) - else -> throw IllegalArgumentException("Cannot set validators from $it state") + is SetupStakingProcess.ChoosingValidators -> it.next(validators, selectionMethod) + SetupStakingProcess.Initial -> throw IllegalArgumentException("Cannot set validators from $it state") } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt index 02e102d352..6dbdb7054e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/ConfirmChangeValidatorsViewModel.kt @@ -33,6 +33,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStak import io.novafoundation.nova.feature_staking_impl.presentation.common.validation.stakingValidationFailure import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.confirm.hints.ConfirmStakeHintsMixinFactory +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.reset import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.awaitDecimalFee import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState @@ -94,7 +95,7 @@ class ConfirmChangeValidatorsViewModel( .share() val nominationsFlow = flowOf { - val selectedCount = currentProcessState.validators.size + val selectedCount = currentProcessState.newValidators.size val maxValidatorsPerNominator = maxValidatorsPerNominator() resourceManager.getString(R.string.staking_confirm_nominations, selectedCount, maxValidatorsPerNominator) @@ -133,7 +134,7 @@ class ConfirmChangeValidatorsViewModel( ) } - private fun prepareNominations() = currentProcessState.validators.map(Validator::accountIdHex) + private fun prepareNominations() = currentProcessState.newValidators.map(Validator::accountIdHex) private fun sendTransactionIfValid() = launch { _showNextProgress.value = true @@ -164,7 +165,7 @@ class ConfirmChangeValidatorsViewModel( if (setupResult.isSuccess) { showMessage(resourceManager.getString(R.string.common_transaction_submitted)) - setupStakingSharedState.set(currentProcessState.reset()) + setupStakingSharedState.reset() router.returnToCurrentValidators() } else { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/nominations/ConfirmNominationsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/nominations/ConfirmNominationsViewModel.kt index 61c666a69f..f8d3a3589f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/nominations/ConfirmNominationsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/confirm/nominations/ConfirmNominationsViewModel.kt @@ -36,7 +36,7 @@ class ConfirmNominationsViewModel( private val currentSetupStakingProcess = sharedStateSetup.get() - private val validators = currentSetupStakingProcess.validators + private val validators = currentSetupStakingProcess.newValidators val selectedValidatorsLiveData = liveData(Dispatchers.Default) { emit(convertToModels(validators, tokenUseCase.currentToken())) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt index 62b56801b3..36c57e3ab6 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/ReviewCustomValidatorsViewModel.kt @@ -45,7 +45,7 @@ class ReviewCustomValidatorsViewModel( .share() private val selectedValidators = confirmSetupState - .map { it.validators } + .map { it.newValidators } .share() private val currentTokenFlow = tokenUseCase.currentTokenFlow() diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/flowAction/SetupStakingReviewValidatorsFlowAction.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/flowAction/SetupStakingReviewValidatorsFlowAction.kt index 6fdfa4c309..c4a3d5e1c5 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/flowAction/SetupStakingReviewValidatorsFlowAction.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/review/flowAction/SetupStakingReviewValidatorsFlowAction.kt @@ -4,7 +4,8 @@ import io.novafoundation.nova.feature_staking_impl.data.StakingOption import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupStakingType.SetupStakingTypeSelectionMixinFactory import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState -import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.getSelectedValidators +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.getNewValidators +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.reset import kotlinx.coroutines.CoroutineScope class SetupStakingReviewValidatorsFlowAction( @@ -15,8 +16,9 @@ class SetupStakingReviewValidatorsFlowAction( override suspend fun execute(coroutineScope: CoroutineScope, stakingOption: StakingOption) { val setupStakingTypeSelectionMixin = setupStakingTypeSelectionMixinFactory.create(coroutineScope) - val selectedValidators = sharedStateSetup.getSelectedValidators() + val selectedValidators = sharedStateSetup.getNewValidators() setupStakingTypeSelectionMixin.selectValidatorsAndApply(selectedValidators, stakingOption) + sharedStateSetup.reset() stakingRouter.finishSetupValidatorsFlow() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/search/SearchCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/search/SearchCustomValidatorsViewModel.kt index cce38da51e..d2af5707be 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/search/SearchCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/search/SearchCustomValidatorsViewModel.kt @@ -49,7 +49,7 @@ class SearchCustomValidatorsViewModel( .share() private val selectedValidators = confirmSetupState - .map { it.validators.toSet() } + .map { it.newValidators.toSet() } .inBackground() .share() diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt index 3c6eac6312..676068addb 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/custom/select/SelectCustomValidatorsViewModel.kt @@ -21,7 +21,6 @@ import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settin import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settings.sortings.TotalStakeSorting import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settings.sortings.ValidatorOwnStakeSorting import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter -import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValidatorToValidatorDetailsParcelModel import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValidatorToValidatorModel @@ -29,6 +28,7 @@ import io.novafoundation.nova.feature_staking_impl.presentation.validators.chang import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.common.CustomValidatorsPayload import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.custom.select.model.ContinueButtonState +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.getSelectedValidatorsOrNull import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.setCustomValidators import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.relaychain @@ -40,11 +40,11 @@ import io.novafoundation.nova.runtime.state.chain import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.emitAll -import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch @@ -221,8 +221,8 @@ class SelectCustomValidatorsViewModel( private fun observeExternalSelectionChanges() { setupStakingSharedState.setupStakingProcess - .filterIsInstance() - .onEach { selectedValidators.value = it.validators.asSetItems() } + .mapNotNull { it.getSelectedValidatorsOrNull() } + .onEach { validators -> selectedValidators.value = validators.asSetItems() } .launchIn(viewModelScope) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt index 00f48fa65f..2fa2e71d1b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/recommended/RecommendedValidatorsViewModel.kt @@ -13,13 +13,12 @@ import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor import io.novafoundation.nova.feature_staking_impl.domain.recommendations.ValidatorRecommenderFactory import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settings.RecommendationSettingsProviderFactory import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter -import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess.ReadyToSubmit -import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess.ReadyToSubmit.SelectionMethod import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValidatorToValidatorDetailsParcelModel import io.novafoundation.nova.feature_staking_impl.presentation.mappers.mapValidatorToValidatorModel import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.ValidatorStakeTargetModel import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.retractRecommended import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.setRecommendedValidators import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.StakeTargetDetailsPayload import io.novafoundation.nova.feature_staking_impl.presentation.validators.details.relaychain @@ -70,7 +69,7 @@ class RecommendedValidatorsViewModel( }.inBackground().share() fun backClicked() { - retractRecommended() + sharedStateSetup.retractRecommended() router.back() } @@ -99,12 +98,4 @@ class RecommendedValidatorsViewModel( mapValidatorToValidatorModel(chain, it, addressIconGenerator, token) } } - - private fun retractRecommended() = sharedStateSetup.mutate { - if (it is ReadyToSubmit && it.selectionMethod == SelectionMethod.RECOMMENDED) { - it.previous() - } else { - it - } - } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/start/StartChangeValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/start/StartChangeValidatorsViewModel.kt index 04fccfb448..f27614c088 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/start/StartChangeValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/start/StartChangeValidatorsViewModel.kt @@ -13,13 +13,13 @@ import io.novafoundation.nova.feature_staking_impl.R import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor import io.novafoundation.nova.feature_staking_impl.domain.recommendations.ValidatorRecommenderFactory import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter -import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.activeStake +import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.getSelectedValidatorsOrEmpty import io.novafoundation.nova.feature_staking_impl.presentation.validators.change.reset import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.transform +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class Texts( @@ -41,32 +41,31 @@ class StartChangeValidatorsViewModel( private val maxValidatorsPerNominator = flowOf { interactor.maxValidatorsPerNominator(setupStakingSharedState.activeStake()) - }.share() + }.shareInBackground() val validatorsLoading = MutableStateFlow(true) - val customValidatorsTexts = setupStakingSharedState.setupStakingProcess.transform { - when { - it is SetupStakingProcess.ReadyToSubmit && it.validators.isNotEmpty() -> emit( - Texts( - toolbarTitle = resourceManager.getString(R.string.staking_change_validators), - selectManuallyTitle = resourceManager.getString(R.string.staking_select_custom), - selectManuallyBadge = resourceManager.getString( - R.string.staking_max_format, - it.validators.size, - maxValidatorsPerNominator.first() - ) + val customValidatorsTexts = setupStakingSharedState.setupStakingProcess.map { + val selectedValidators = it.getSelectedValidatorsOrEmpty() + + if (selectedValidators.isNotEmpty()) { + Texts( + toolbarTitle = resourceManager.getString(R.string.staking_change_validators), + selectManuallyTitle = resourceManager.getString(R.string.staking_select_custom), + selectManuallyBadge = resourceManager.getString( + R.string.staking_max_format, + selectedValidators.size, + maxValidatorsPerNominator.first() ) ) - else -> emit( - Texts( - toolbarTitle = resourceManager.getString(R.string.staking_set_validators), - selectManuallyTitle = resourceManager.getString(R.string.staking_select_custom), - selectManuallyBadge = null - ) + } else { + Texts( + toolbarTitle = resourceManager.getString(R.string.staking_set_validators), + selectManuallyTitle = resourceManager.getString(R.string.staking_select_custom), + selectManuallyBadge = null ) } - } + }.shareInBackground() init { launch { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/CurrentValidatorsViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/CurrentValidatorsViewModel.kt index 89b55eeb24..8ec5486d08 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/CurrentValidatorsViewModel.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/current/CurrentValidatorsViewModel.kt @@ -8,7 +8,6 @@ import io.novafoundation.nova.common.mixin.api.Validatable import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.combineToPair import io.novafoundation.nova.common.utils.flowOf -import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.toHexAccountId import io.novafoundation.nova.common.utils.withLoading import io.novafoundation.nova.common.validation.ValidationExecutor @@ -21,7 +20,6 @@ import io.novafoundation.nova.feature_staking_impl.domain.validations.controller import io.novafoundation.nova.feature_staking_impl.domain.validators.current.CurrentValidatorsInteractor import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess -import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingProcess.ReadyToSubmit.SelectionMethod import io.novafoundation.nova.feature_staking_impl.presentation.common.SetupStakingSharedState import io.novafoundation.nova.feature_staking_impl.presentation.common.currentStakeTargets.CurrentStakeTargetsViewModel import io.novafoundation.nova.feature_staking_impl.presentation.common.currentStakeTargets.model.SelectedStakeTargetModel @@ -64,8 +62,7 @@ class CurrentValidatorsViewModel( private val stashFlow = stakingInteractor.selectedAccountStakingStateFlow(viewModelScope) .filterIsInstance() - .inBackground() - .share() + .shareInBackground() private val assetFlow = assetUseCase.currentAssetFlow() .shareInBackground() @@ -76,13 +73,11 @@ class CurrentValidatorsViewModel( private val groupedCurrentValidatorsFlow = combineToPair(stashFlow, activeStakeFlow) .flatMapLatest { (stash, activeStake) -> currentValidatorsInteractor.nominatedValidatorsFlow(stash, activeStake, viewModelScope) } - .inBackground() - .share() + .shareInBackground() private val flattenCurrentValidators = groupedCurrentValidatorsFlow .map { it.toValueList() } - .inBackground() - .share() + .shareInBackground() private val tokenFlow = assetFlow .map { it.token } @@ -150,10 +145,9 @@ class CurrentValidatorsViewModel( private fun openStartChangeValidators() { launch { - val currentState = setupStakingSharedState.get() val currentValidators = flattenCurrentValidators.first().map(NominatedValidator::validator) val activeStake = activeStakeFlow.first() - val newState = currentState.next(activeStake, currentValidators, SelectionMethod.CUSTOM) + val newState = SetupStakingProcess.Initial.next(activeStake, currentValidators) setupStakingSharedState.set(newState) router.openStartChangeValidators() } From d03b4f6e80aa1e41d27ff356be1f90c73497d522 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Fri, 29 Dec 2023 12:14:25 +0300 Subject: [PATCH 077/100] Fix - crash for proxied receive (#1304) --- .../data/repository/AccountRepositoryImpl.kt | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index 161c2057ff..ade4b5d179 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -25,18 +25,16 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrderi import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn import io.novafoundation.nova.feature_account_api.domain.model.addressIn import io.novafoundation.nova.feature_account_api.domain.model.multiChainEncryptionIn -import io.novafoundation.nova.feature_account_api.domain.model.publicKeyIn +import io.novafoundation.nova.feature_account_api.domain.model.requireAddressIn import io.novafoundation.nova.feature_account_impl.data.mappers.mapNodeLocalToNode import io.novafoundation.nova.feature_account_impl.data.network.blockchain.AccountSubstrateSource import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource import io.novafoundation.nova.runtime.ext.genesisHash -import io.novafoundation.nova.runtime.ext.isValidAddress import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.qr.MultiChainQrSharingFactory import jp.co.soramitsu.fearless_utils.encrypt.json.JsonSeedEncoder import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.Mnemonic import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.MnemonicCreator -import jp.co.soramitsu.fearless_utils.encrypt.qr.QrFormat import jp.co.soramitsu.fearless_utils.runtime.AccountId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow @@ -308,18 +306,10 @@ class AccountRepositoryImpl( } override suspend fun createQrAccountContent(chain: Chain, account: MetaAccount): String { - val payload = QrFormat.Payload( - address = account.addressIn(chain)!!, - publicKey = account.publicKeyIn(chain)!!, - name = account.name - ) - - val qrSharing = multiChainQrSharingFactory.create(addressValidator = chain::isValidAddress) - - return qrSharing.encode(payload) + return account.requireAddressIn(chain) } - private suspend fun mapAccountLocalToAccount(accountLocal: AccountLocal): Account { + private fun mapAccountLocalToAccount(accountLocal: AccountLocal): Account { val network = getNetworkForType(accountLocal.networkType) return with(accountLocal) { From 091c02bdb621722deb1e25e5e77ca4232baba239 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Fri, 29 Dec 2023 13:15:46 +0300 Subject: [PATCH 078/100] Fix - proxy icon opacity (#1305) --- .../data/proxy/RealProxySyncService.kt | 2 -- .../listing/DelegatedMetaAccountUpdatesListingMixin.kt | 9 +++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 1bec2fd494..17f5853a56 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -28,8 +28,6 @@ import io.novafoundation.nova.runtime.multiNetwork.findChains import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -private const val SYNC_TIMEOUT = 10_000L // 10 seconds - class RealProxySyncService( private val chainRegistry: ChainRegistry, private val proxyRepository: ProxyRepository, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt index 0467c7d53a..0fa5cfd3c1 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/DelegatedMetaAccountUpdatesListingMixin.kt @@ -35,7 +35,8 @@ class DelegatedMetaAccountUpdatesListingMixinFactory( } } -private const val ICON_ALPHA = 0.56f +private const val DISABLED_ICON_ALPHA = 0.56f +private const val ENABLED_ICON_ALPHA = 1.0f private class DelegatedMetaAccountUpdatesListingMixin( private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, @@ -75,11 +76,11 @@ private class DelegatedMetaAccountUpdatesListingMixin( subtitle = if (isEnabled) subtitle else subtitle.toSpannable(colorSpan(secondaryColor)), isSelected = false, isClickable = true, - picture = if (isEnabled) walletIcon else walletIcon.withAlphaDrawable(ICON_ALPHA), + picture = if (isEnabled) walletIcon else walletIcon.withAlphaDrawable(DISABLED_ICON_ALPHA), chainIconUrl = proxiedWithProxy.chain.icon, updateIndicator = false, subtitleIconRes = null, - chainIconOpacity = ICON_ALPHA, + chainIconOpacity = if (isEnabled) ENABLED_ICON_ALPHA else DISABLED_ICON_ALPHA, isEditable = false ) } @@ -92,7 +93,7 @@ private class DelegatedMetaAccountUpdatesListingMixin( val proxyIcon = proxyFormatter.makeAccountDrawable(proxiedWithProxy.proxy) return proxyFormatter.mapProxyMetaAccountSubtitle( proxiedWithProxy.proxy.name, - if (isEnabled) proxyIcon else proxyIcon.withAlphaDrawable(ICON_ALPHA), + if (isEnabled) proxyIcon else proxyIcon.withAlphaDrawable(DISABLED_ICON_ALPHA), proxy ) } From 884249ad14d40b6c7b52cae777ef0d6dfd978889 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Wed, 3 Jan 2024 23:01:44 +0100 Subject: [PATCH 079/100] Fixed proxy types matching --- .../nova/common/utils/FearlessLibExt.kt | 11 +++ .../signer/proxy/ModuleToProxyTypeMatcher.kt | 51 ---------- .../data/signer/proxy/ProxiedSigner.kt | 13 +-- .../signer/proxy/ProxyCallFilterFactory.kt | 95 +++++++++++++++++++ .../signer/proxy/callFilter/CallFilter.kt | 7 ++ .../proxy/callFilter/CompoundCallFilter.kt | 14 +++ .../proxy/callFilter/EverythingFilter.kt | 10 ++ .../proxy/callFilter/WhiteListFilter.kt | 20 ++++ .../di/modules/signers/ProxiedSignerModule.kt | 9 +- 9 files changed, 172 insertions(+), 58 deletions(-) delete mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CallFilter.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CompoundCallFilter.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/EverythingFilter.kt create mode 100644 feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/WhiteListFilter.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index 4bba2b743e..da28c696d1 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -369,4 +369,15 @@ object Modules { const val PROXY = "Proxy" const val AUCTIONS = "auctions" + + const val INDICES = "Indices" + const val GRANDPA = "Grandpa" + const val IM_ONLINE = "ImOnline" + const val BOUNTIES = "Bounties" + const val CHILD_BOUNTIES = "ChildBounties" + const val WHITELIST = "Whitelist" + const val CLAIMS = "Claims" + const val MULTISIG = "Multisig" + const val REGISTRAR = "Registrar" + const val FAST_UNSTAKE = "FastUnstake" } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt deleted file mode 100644 index e551728e7b..0000000000 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ModuleToProxyTypeMatcher.kt +++ /dev/null @@ -1,51 +0,0 @@ -package io.novafoundation.nova.feature_account_impl.data.signer.proxy - -import io.novafoundation.nova.common.utils.Modules -import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount -import jp.co.soramitsu.fearless_utils.runtime.metadata.module.Module - -class ModuleToProxyTypeMatcher(private val module: String) { - - fun matchToProxyTypes(proxyTypes: List): ProxyAccount.ProxyType? { - if (proxyTypes.isEmpty()) return null - - return proxyTypes.firstOrNull { getModulesSupportedByProxyType(it).isSupporting(module) } - } - - private fun getModulesSupportedByProxyType(proxyType: ProxyAccount.ProxyType): SupportedModules { - return when (proxyType) { - ProxyAccount.ProxyType.Any, - is ProxyAccount.ProxyType.Other -> SupportedModules.AnyModule - - ProxyAccount.ProxyType.NonTransfer -> SupportedModules.SpeificModules(Modules.STAKING, Modules.REFERENDA, Modules.NOMINATION_POOLS) - ProxyAccount.ProxyType.Governance -> SupportedModules.SpeificModules(Modules.REFERENDA) - ProxyAccount.ProxyType.Staking -> SupportedModules.SpeificModules(Modules.STAKING) - ProxyAccount.ProxyType.IdentityJudgement -> SupportedModules.SpeificModules(Modules.IDENTITY) - ProxyAccount.ProxyType.CancelProxy -> SupportedModules.SpeificModules(Modules.PROXY) - ProxyAccount.ProxyType.Auction -> SupportedModules.SpeificModules(Modules.AUCTIONS) - ProxyAccount.ProxyType.NominationPools -> SupportedModules.SpeificModules(Modules.NOMINATION_POOLS) - } - } -} - -private sealed interface SupportedModules { - - fun isSupporting(module: String): Boolean - - class SpeificModules(vararg modules: String) : SupportedModules { - - private val modulesSet = modules.toSet() - - override fun isSupporting(module: String): Boolean { - return modulesSet.contains(module) - } - } - - object AnyModule : SupportedModules { - override fun isSupporting(module: String) = true - } -} - -fun Module.toProxyTypeMatcher(): ModuleToProxyTypeMatcher { - return ModuleToProxyTypeMatcher(name) -} 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 79404825c6..12f08682ef 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 @@ -33,7 +33,8 @@ class ProxiedSignerFactory( private val proxySigningPresenter: ProxySigningPresenter, private val proxyRepository: ProxyRepository, private val rpcCalls: RpcCalls, - private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus + private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus, + private val proxyCallFilterFactory: ProxyCallFilterFactory ) { fun create(metaAccount: MetaAccount, signerProvider: SignerProvider, isRoot: Boolean): ProxiedSigner { @@ -46,7 +47,8 @@ class ProxiedSignerFactory( proxyRepository = proxyRepository, rpcCalls = rpcCalls, proxyExtrinsicValidationEventBus = proxyExtrinsicValidationEventBus, - isRootProxied = isRoot + isRootProxied = isRoot, + proxyCallFilterFactory = proxyCallFilterFactory ) } } @@ -60,7 +62,8 @@ class ProxiedSigner( private val proxyRepository: ProxyRepository, private val rpcCalls: RpcCalls, private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus, - private val isRootProxied: Boolean + private val isRootProxied: Boolean, + private val proxyCallFilterFactory: ProxyCallFilterFactory ) : NovaSigner { override suspend fun signerAccountId(chain: Chain): AccountId { @@ -131,10 +134,8 @@ class ProxiedSigner( ) val callInstance = payload.call.toCallInstance() ?: signingNotSupported() - val module = callInstance.call.module - val proxyType = module.toProxyTypeMatcher() - .matchToProxyTypes(availableProxyTypes) + val proxyType = proxyCallFilterFactory.getFirstMatchedTypeOrNull(callInstance.call, availableProxyTypes) ?: notEnoughPermission(proxyMetaAccount, availableProxyTypes) val proxyAddress = proxyMetaAccount.requireAddressIn(chain) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt new file mode 100644 index 0000000000..91879b3fa2 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt @@ -0,0 +1,95 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy + +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.CallFilter +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.CompoundCallFilter +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.EverythingFilter +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.WhiteListFilter +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +class ProxyCallFilterFactory { + + fun getCallFilterFor(proxyType: ProxyAccount.ProxyType): CallFilter { + return when (proxyType) { + ProxyAccount.ProxyType.Any, + is ProxyAccount.ProxyType.Other -> EverythingFilter() + + ProxyAccount.ProxyType.NonTransfer -> CompoundCallFilter( + WhiteListFilter(Modules.SYSTEM), + WhiteListFilter(Modules.SCHEDULER), + WhiteListFilter(Modules.BABE), + WhiteListFilter(Modules.TIMESTAMP), + WhiteListFilter(Modules.INDICES, listOf("claim", "free", "freeze")), + WhiteListFilter(Modules.STAKING), + WhiteListFilter(Modules.SESSION), + WhiteListFilter(Modules.GRANDPA), + WhiteListFilter(Modules.IM_ONLINE), + WhiteListFilter(Modules.TREASURY), + WhiteListFilter(Modules.BOUNTIES), + WhiteListFilter(Modules.CHILD_BOUNTIES), + WhiteListFilter(Modules.CONVICTION_VOTING), + WhiteListFilter(Modules.REFERENDA), + WhiteListFilter(Modules.WHITELIST), + WhiteListFilter(Modules.CLAIMS), + WhiteListFilter(Modules.VESTING, listOf("vest", "vest_other")), + WhiteListFilter(Modules.UTILITY), + WhiteListFilter(Modules.IDENTITY), + WhiteListFilter(Modules.PROXY), + WhiteListFilter(Modules.MULTISIG), + WhiteListFilter(Modules.REGISTRAR, listOf("register", "deregister", "reserve")), + WhiteListFilter(Modules.CROWDLOAN), + WhiteListFilter(Modules.SLOTS), + WhiteListFilter(Modules.AUCTIONS), + WhiteListFilter(Modules.VOTER_LIST), + WhiteListFilter(Modules.NOMINATION_POOLS), + WhiteListFilter(Modules.FAST_UNSTAKE) + ) + + ProxyAccount.ProxyType.Governance -> CompoundCallFilter( + WhiteListFilter(Modules.TREASURY), + WhiteListFilter(Modules.BOUNTIES), + WhiteListFilter(Modules.UTILITY), + WhiteListFilter(Modules.CHILD_BOUNTIES), + WhiteListFilter(Modules.CONVICTION_VOTING), + WhiteListFilter(Modules.REFERENDA), + WhiteListFilter(Modules.WHITELIST) + ) + + ProxyAccount.ProxyType.Staking -> CompoundCallFilter( + WhiteListFilter(Modules.STAKING), + WhiteListFilter(Modules.SESSION), + WhiteListFilter(Modules.UTILITY), + WhiteListFilter(Modules.FAST_UNSTAKE), + WhiteListFilter(Modules.VOTER_LIST), + WhiteListFilter(Modules.NOMINATION_POOLS) + ) + + ProxyAccount.ProxyType.NominationPools -> CompoundCallFilter( + WhiteListFilter(Modules.NOMINATION_POOLS), + WhiteListFilter(Modules.UTILITY) + ) + + ProxyAccount.ProxyType.IdentityJudgement -> CompoundCallFilter( + WhiteListFilter(Modules.IDENTITY, listOf("provide_judgement")), + WhiteListFilter(Modules.UTILITY) + ) + + ProxyAccount.ProxyType.CancelProxy -> WhiteListFilter(Modules.PROXY, listOf("reject_announcement")) + + ProxyAccount.ProxyType.Auction -> CompoundCallFilter( + WhiteListFilter(Modules.AUCTIONS), + WhiteListFilter(Modules.CROWDLOAN), + WhiteListFilter(Modules.REGISTRAR), + WhiteListFilter(Modules.SLOTS) + ) + } + } +} + +fun ProxyCallFilterFactory.getFirstMatchedTypeOrNull(call: GenericCall.Instance, proxyTypes: List): ProxyAccount.ProxyType? { + return proxyTypes.firstOrNull { + val callFilter = this.getCallFilterFor(it) + callFilter.canExecute(call) + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CallFilter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CallFilter.kt new file mode 100644 index 0000000000..24c5ec2188 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CallFilter.kt @@ -0,0 +1,7 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter + +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +interface CallFilter { + fun canExecute(call: GenericCall.Instance): Boolean +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CompoundCallFilter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CompoundCallFilter.kt new file mode 100644 index 0000000000..b04ffaaa55 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CompoundCallFilter.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter + +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +class CompoundCallFilter( + private val filters: List +) : CallFilter { + + constructor(vararg filters: CallFilter) : this(filters.toList()) + + override fun canExecute(call: GenericCall.Instance): Boolean { + return filters.all { it.canExecute(call) } + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/EverythingFilter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/EverythingFilter.kt new file mode 100644 index 0000000000..326e6b2b05 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/EverythingFilter.kt @@ -0,0 +1,10 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter + +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +class EverythingFilter : CallFilter { + + override fun canExecute(call: GenericCall.Instance): Boolean { + return true + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/WhiteListFilter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/WhiteListFilter.kt new file mode 100644 index 0000000000..e5029ac459 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/WhiteListFilter.kt @@ -0,0 +1,20 @@ +package io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter + +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall + +class WhiteListFilter(private val matchingModule: String, private val matchingCalls: List?) : CallFilter { + + constructor(matchingModule: String) : this(matchingModule, null) + + override fun canExecute(call: GenericCall.Instance): Boolean { + val callModule = call.module.name + val callName = call.function.name + + if (matchingModule == callModule) { + if (matchingCalls == null) return true + if (matchingCalls.contains(callName)) return true + } + + return false + } +} diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt index 33e103dbf8..a2e0c136c4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/ProxiedSignerModule.kt @@ -8,6 +8,7 @@ import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepos import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxiedSignerFactory import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.ProxyCallFilterFactory import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.network.rpc.RpcCalls @@ -18,6 +19,10 @@ class ProxiedSignerModule { @FeatureScope fun provideProxyExtrinsicValidationRequestBus() = ProxyExtrinsicValidationRequestBus() + @Provides + @FeatureScope + fun provideProxyCallFilterFactory() = ProxyCallFilterFactory() + @Provides @FeatureScope fun provideProxiedSignerFactory( @@ -27,12 +32,14 @@ class ProxiedSignerModule { proxyRepository: ProxyRepository, rpcCalls: RpcCalls, proxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus, + proxyCallFilterFactory: ProxyCallFilterFactory ) = ProxiedSignerFactory( chainRegistry, accountRepository, proxySigningPresenter, proxyRepository, rpcCalls, - proxyExtrinsicValidationRequestBus + proxyExtrinsicValidationRequestBus, + proxyCallFilterFactory ) } From 056121c5fa5588c9e78af778894cadf8544b6f2b Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 4 Jan 2024 10:45:59 +0100 Subject: [PATCH 080/100] Fixed pr notes --- .../data/signer/proxy/ProxyCallFilterFactory.kt | 14 +++++++------- .../{CompoundCallFilter.kt => AnyOfCallFilter.kt} | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) rename feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/{CompoundCallFilter.kt => AnyOfCallFilter.kt} (82%) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt index 91879b3fa2..df6404d7cb 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/ProxyCallFilterFactory.kt @@ -3,7 +3,7 @@ package io.novafoundation.nova.feature_account_impl.data.signer.proxy import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.CallFilter -import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.CompoundCallFilter +import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.AnyOfCallFilter import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.EverythingFilter import io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter.WhiteListFilter import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall @@ -15,7 +15,7 @@ class ProxyCallFilterFactory { ProxyAccount.ProxyType.Any, is ProxyAccount.ProxyType.Other -> EverythingFilter() - ProxyAccount.ProxyType.NonTransfer -> CompoundCallFilter( + ProxyAccount.ProxyType.NonTransfer -> AnyOfCallFilter( WhiteListFilter(Modules.SYSTEM), WhiteListFilter(Modules.SCHEDULER), WhiteListFilter(Modules.BABE), @@ -46,7 +46,7 @@ class ProxyCallFilterFactory { WhiteListFilter(Modules.FAST_UNSTAKE) ) - ProxyAccount.ProxyType.Governance -> CompoundCallFilter( + ProxyAccount.ProxyType.Governance -> AnyOfCallFilter( WhiteListFilter(Modules.TREASURY), WhiteListFilter(Modules.BOUNTIES), WhiteListFilter(Modules.UTILITY), @@ -56,7 +56,7 @@ class ProxyCallFilterFactory { WhiteListFilter(Modules.WHITELIST) ) - ProxyAccount.ProxyType.Staking -> CompoundCallFilter( + ProxyAccount.ProxyType.Staking -> AnyOfCallFilter( WhiteListFilter(Modules.STAKING), WhiteListFilter(Modules.SESSION), WhiteListFilter(Modules.UTILITY), @@ -65,19 +65,19 @@ class ProxyCallFilterFactory { WhiteListFilter(Modules.NOMINATION_POOLS) ) - ProxyAccount.ProxyType.NominationPools -> CompoundCallFilter( + ProxyAccount.ProxyType.NominationPools -> AnyOfCallFilter( WhiteListFilter(Modules.NOMINATION_POOLS), WhiteListFilter(Modules.UTILITY) ) - ProxyAccount.ProxyType.IdentityJudgement -> CompoundCallFilter( + ProxyAccount.ProxyType.IdentityJudgement -> AnyOfCallFilter( WhiteListFilter(Modules.IDENTITY, listOf("provide_judgement")), WhiteListFilter(Modules.UTILITY) ) ProxyAccount.ProxyType.CancelProxy -> WhiteListFilter(Modules.PROXY, listOf("reject_announcement")) - ProxyAccount.ProxyType.Auction -> CompoundCallFilter( + ProxyAccount.ProxyType.Auction -> AnyOfCallFilter( WhiteListFilter(Modules.AUCTIONS), WhiteListFilter(Modules.CROWDLOAN), WhiteListFilter(Modules.REGISTRAR), diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CompoundCallFilter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/AnyOfCallFilter.kt similarity index 82% rename from feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CompoundCallFilter.kt rename to feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/AnyOfCallFilter.kt index b04ffaaa55..637ad90e6e 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/CompoundCallFilter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/callFilter/AnyOfCallFilter.kt @@ -2,13 +2,13 @@ package io.novafoundation.nova.feature_account_impl.data.signer.proxy.callFilter import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall -class CompoundCallFilter( +class AnyOfCallFilter( private val filters: List ) : CallFilter { constructor(vararg filters: CallFilter) : this(filters.toList()) override fun canExecute(call: GenericCall.Instance): Boolean { - return filters.all { it.canExecute(call) } + return filters.any { it.canExecute(call) } } } From babe4d898cd5d2388e9df2fce0096148157654f8 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 4 Jan 2024 14:39:37 +0100 Subject: [PATCH 081/100] Selecting first meta account in case when selected proxied is removed due to deactivation --- .../feature_account_impl/domain/AccountInteractorImpl.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt index 8aac67759a..9e87a430cd 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt @@ -182,5 +182,12 @@ class AccountInteractorImpl( override suspend fun removeDeactivatedMetaAccounts() { accountRepository.removeDeactivatedMetaAccounts() + + if (!accountRepository.isAccountSelected()) { + val metaAccounts = getMetaAccounts() + if (metaAccounts.isNotEmpty()) { + accountRepository.selectMetaAccount(metaAccounts.first().id) + } + } } } From 8095a0c805e637ccc419b3c6b331c13aa35d00c5 Mon Sep 17 00:00:00 2001 From: antonijzelinskij <107959809+antonijzelinskij@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:33:49 +0500 Subject: [PATCH 082/100] Use on chain balance for proxy fee validation (#1310) * Use on chain balance for proxy fee validation * Rename variables --- .../network/runtime/binding/AccountInfo.kt | 24 ++++++++++++++++--- .../blockhain/assets/balances/AssetBalance.kt | 7 ++++++ .../ProxyHaveEnoughFeeValidation.kt | 16 +++++++++---- .../balances/UnsupportedAssetBalance.kt | 2 ++ .../equilibrium/EquilibriumAssetBalance.kt | 17 +++++++++++-- .../balances/evmErc20/EvmErc20AssetBalance.kt | 15 +++++++++--- .../evmNative/EvmNativeAssetBalance.kt | 14 +++++++++-- .../assets/balances/orml/OrmlAssetBalance.kt | 21 +++++++++------- .../{OrmlAssets.kt => OrmlBalanceBinding.kt} | 24 +++---------------- .../statemine/StatemineAssetBalance.kt | 19 +++++++++++++-- .../balances/utility/NativeAssetBalance.kt | 9 +++++-- 11 files changed, 121 insertions(+), 47 deletions(-) rename feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/{OrmlAssets.kt => OrmlBalanceBinding.kt} (72%) diff --git a/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/AccountInfo.kt b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/AccountInfo.kt index fe4d27b0f8..cb5b0d9665 100644 --- a/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/AccountInfo.kt +++ b/common/src/main/java/io/novafoundation/nova/common/data/network/runtime/binding/AccountInfo.kt @@ -8,12 +8,30 @@ import jp.co.soramitsu.fearless_utils.runtime.definitions.types.fromHexOrNull import jp.co.soramitsu.fearless_utils.runtime.metadata.storage import java.math.BigInteger -class AccountData( +open class AccountBalance( val free: BigInteger, val reserved: BigInteger, - val frozen: BigInteger, + val frozen: BigInteger +) { + + companion object { + + fun empty(): AccountBalance { + return AccountBalance( + free = BigInteger.ZERO, + reserved = BigInteger.ZERO, + frozen = BigInteger.ZERO, + ) + } + } +} + +class AccountData( + free: BigInteger, + reserved: BigInteger, + frozen: BigInteger, val flags: AccountDataFlags, -) +) : AccountBalance(free, reserved, frozen) @JvmInline value class AccountDataFlags(val value: BigInteger) { diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/balances/AssetBalance.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/balances/AssetBalance.kt index 208365a2ed..abc18282d9 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/balances/AssetBalance.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/data/network/blockhain/assets/balances/AssetBalance.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.balances +import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance import io.novafoundation.nova.common.data.network.runtime.binding.BlockHash import io.novafoundation.nova.core.updater.SharedRequestsBuilder import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount @@ -35,6 +36,12 @@ interface AssetBalance { chainAsset: Chain.Asset ): BigInteger + suspend fun queryAccountBalance( + chain: Chain, + chainAsset: Chain.Asset, + accountId: AccountId + ): AccountBalance + suspend fun queryTotalBalance( chain: Chain, chainAsset: Chain.Asset, diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt index 424d3e4809..8ff9583283 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/validation/ProxyHaveEnoughFeeValidation.kt @@ -10,9 +10,10 @@ import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicServic import io.novafoundation.nova.feature_account_api.data.model.Fee import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.AssetSourceRegistry -import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.existentialDepositInPlanks import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository +import io.novafoundation.nova.feature_wallet_api.domain.model.Asset.Companion.calculateBalanceCountedTowardsEd +import io.novafoundation.nova.feature_wallet_api.domain.model.Asset.Companion.calculateTransferable import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import jp.co.soramitsu.fearless_utils.runtime.AccountId @@ -59,9 +60,16 @@ class ProxyHaveEnoughFeeValidation( val chainAsset = chainWithAsset(value).asset val fee = calculateFee(metaAccount(value), chain, call(value)) val asset = walletRepository.getAsset(proxyAccountId(value), chainAsset)!! - val existentialDeposit = assetSourceRegistry.existentialDepositInPlanks(chain, asset.token.configuration) - val transferable = asset.transferableInPlanks - val balanceWithoutEd = (asset.balanceCountedTowardsEDInPlanks - existentialDeposit).atLeastZero() + + val assetSource = assetSourceRegistry.sourceFor(chainAsset) + val assetBalanceSource = assetSource.balance + + val accountData = assetBalanceSource.queryAccountBalance(chain, chainAsset, proxyAccountId(value)) + + val existentialDeposit = assetBalanceSource.existentialDeposit(chain, chainAsset) + val transferable = asset.transferableMode.calculateTransferable(accountData.free, accountData.frozen, accountData.reserved) + val balanceCountedTowardsEd = asset.edCountingMode.calculateBalanceCountedTowardsEd(accountData.free, accountData.reserved) + val balanceWithoutEd = (balanceCountedTowardsEd - existentialDeposit).atLeastZero() return validOrError(transferable >= fee.amount && balanceWithoutEd >= fee.amount) { proxyNotEnoughFee(value, transferable, fee) diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/UnsupportedAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/UnsupportedAssetBalance.kt index b46f59fd51..c284688ced 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/UnsupportedAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/UnsupportedAssetBalance.kt @@ -23,6 +23,8 @@ class UnsupportedAssetBalance : AssetBalance { override suspend fun existentialDeposit(chain: Chain, chainAsset: Chain.Asset) = unsupported() + override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId) = unsupported() + override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId) = unsupported() override suspend fun startSyncingBalance( diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/equilibrium/EquilibriumAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/equilibrium/EquilibriumAssetBalance.kt index 4c3601be46..e14d88cc95 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/equilibrium/EquilibriumAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/equilibrium/EquilibriumAssetBalance.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.equilibrium import android.util.Log +import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance import io.novafoundation.nova.common.data.network.runtime.binding.BlockHash import io.novafoundation.nova.common.data.network.runtime.binding.HelperBinding import io.novafoundation.nova.common.data.network.runtime.binding.UseCaseBinding @@ -102,7 +103,7 @@ class EquilibriumAssetBalance( } } - override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): AccountBalance { val assetBalances = remoteStorageSource.query( chain.id, keyBuilder = { it.getAccountStorage().storageKey(it, accountId) }, @@ -120,7 +121,19 @@ class EquilibriumAssetBalance( .firstOrNull { it.assetId == chainAsset.id } ?.balance .orZero() - return assetBalance + reservedBalance + + val lockedBalance = assetBalances.lock.orZero().takeIf { chainAsset.isUtilityAsset } ?: BigInteger.ZERO + + return AccountBalance( + free = assetBalance, + reserved = reservedBalance, + frozen = lockedBalance + ) + } + + override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + val accountBalance = queryAccountBalance(chain, chainAsset, accountId) + return accountBalance.free + accountBalance.reserved } override suspend fun startSyncingBalance( diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmErc20/EvmErc20AssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmErc20/EvmErc20AssetBalance.kt index b787294580..2a239fc2f4 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmErc20/EvmErc20AssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmErc20/EvmErc20AssetBalance.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.evmErc20 +import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance import io.novafoundation.nova.core.ethereum.log.Topic import io.novafoundation.nova.core.updater.EthereumSharedRequestsBuilder import io.novafoundation.nova.core.updater.SharedRequestsBuilder @@ -64,14 +65,22 @@ class EvmErc20AssetBalance( return BigInteger.ZERO } - override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): AccountBalance { val erc20Type = chainAsset.requireErc20() val ethereumApi = chainRegistry.getCallEthereumApiOrThrow(chain.id) val accountAddress = chain.addressOf(accountId) - - return erc20Standard.querySingle(erc20Type.contractAddress, ethereumApi) + val balance = erc20Standard.querySingle(erc20Type.contractAddress, ethereumApi) .balanceOfAsync(accountAddress) .await() + return AccountBalance( + free = balance, + reserved = BigInteger.ZERO, + frozen = BigInteger.ZERO, + ) + } + + override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + return queryAccountBalance(chain, chainAsset, accountId).free } override suspend fun startSyncingBalance( diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmNative/EvmNativeAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmNative/EvmNativeAssetBalance.kt index af7fab3bb3..d8f089a4b1 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmNative/EvmNativeAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/evmNative/EvmNativeAssetBalance.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.evmNative +import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance import io.novafoundation.nova.core.ethereum.Web3Api import io.novafoundation.nova.core.updater.SharedRequestsBuilder import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount @@ -49,10 +50,19 @@ class EvmNativeAssetBalance( return BigInteger.ZERO } - override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): AccountBalance { val ethereumApi = chainRegistry.getCallEthereumApiOrThrow(chain.id) - return ethereumApi.getLatestNativeBalance(chain.addressOf(accountId)) + val balance = ethereumApi.getLatestNativeBalance(chain.addressOf(accountId)) + return AccountBalance( + free = balance, + reserved = BigInteger.ZERO, + frozen = BigInteger.ZERO, + ) + } + + override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + return queryAccountBalance(chain, chainAsset, accountId).free } override suspend fun startSyncingBalance( diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssetBalance.kt index 86dc8c1aa9..717b9064e1 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssetBalance.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.orml +import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance import io.novafoundation.nova.common.utils.decodeValue import io.novafoundation.nova.common.utils.tokens import io.novafoundation.nova.core.updater.SharedRequestsBuilder @@ -61,14 +62,18 @@ class OrmlAssetBalance( return chainAsset.requireOrml().existentialDeposit } - override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { - val ormlAccountData = remoteStorageSource.query( + override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): AccountBalance { + return remoteStorageSource.query( chainId = chain.id, keyBuilder = { it.ormlBalanceKey(accountId, chainAsset) }, - binding = { scale, runtime -> bindOrmlAccountDataOrEmpty(scale, runtime) } + binding = { scale, runtime -> bindOrmlAccountBalanceOrEmpty(scale, runtime) } ) + } + + override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + val accountBalance = queryAccountBalance(chain, chainAsset, accountId) - return ormlAccountData.free + ormlAccountData.reserved + return accountBalance.free + accountBalance.reserved } override suspend fun startSyncingBalance( @@ -82,7 +87,7 @@ class OrmlAssetBalance( return subscriptionBuilder.subscribe(runtime.ormlBalanceKey(accountId, chainAsset)) .map { - val ormlAccountData = bindOrmlAccountDataOrEmpty(it.value, runtime) + val ormlAccountData = bindOrmlAccountBalanceOrEmpty(it.value, runtime) val assetChanged = updateAssetBalance(metaAccount.id, chainAsset, ormlAccountData) @@ -97,7 +102,7 @@ class OrmlAssetBalance( private suspend fun updateAssetBalance( metaId: Long, chainAsset: Chain.Asset, - ormlAccountData: OrmlAccountData + ormlAccountData: AccountBalance ) = assetCache.updateAsset(metaId, chainAsset) { local -> with(ormlAccountData) { local.copy( @@ -114,7 +119,7 @@ class OrmlAssetBalance( return metadata.tokens().storage("Accounts").storageKey(this, accountId, chainAsset.ormlCurrencyId(this)) } - private fun bindOrmlAccountDataOrEmpty(scale: String?, runtime: RuntimeSnapshot): OrmlAccountData { - return scale?.let { bindOrmlAccountData(it, runtime) } ?: OrmlAccountData.empty() + private fun bindOrmlAccountBalanceOrEmpty(scale: String?, runtime: RuntimeSnapshot): AccountBalance { + return scale?.let { bindOrmlAccountData(it, runtime) } ?: AccountBalance.empty() } } diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssets.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlBalanceBinding.kt similarity index 72% rename from feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssets.kt rename to feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlBalanceBinding.kt index 45cb65fc81..5343fd9486 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlAssets.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/orml/OrmlBalanceBinding.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.orml +import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance import io.novafoundation.nova.common.data.network.runtime.binding.UseCaseBinding import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber import io.novafoundation.nova.common.data.network.runtime.binding.cast @@ -9,33 +10,14 @@ import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.Struct import jp.co.soramitsu.fearless_utils.runtime.definitions.types.fromHexOrNull import jp.co.soramitsu.fearless_utils.runtime.metadata.storage -import java.math.BigInteger - -class OrmlAccountData( - val free: BigInteger, - val reserved: BigInteger, - val frozen: BigInteger, -) { - - companion object { - - fun empty(): OrmlAccountData { - return OrmlAccountData( - free = BigInteger.ZERO, - reserved = BigInteger.ZERO, - frozen = BigInteger.ZERO, - ) - } - } -} @UseCaseBinding -fun bindOrmlAccountData(scale: String, runtime: RuntimeSnapshot): OrmlAccountData { +fun bindOrmlAccountData(scale: String, runtime: RuntimeSnapshot): AccountBalance { val type = runtime.metadata.tokens().storage("Accounts").returnType() val dynamicInstance = type.fromHexOrNull(runtime, scale).cast() - return OrmlAccountData( + return AccountBalance( free = bindNumber(dynamicInstance["free"]), reserved = bindNumber(dynamicInstance["reserved"]), frozen = bindNumber(dynamicInstance["frozen"]), diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/statemine/StatemineAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/statemine/StatemineAssetBalance.kt index 0f8382c3e7..e086024a17 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/statemine/StatemineAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/statemine/StatemineAssetBalance.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.statemine +import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance import io.novafoundation.nova.common.utils.decodeValue import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core.updater.SharedRequestsBuilder @@ -55,7 +56,7 @@ class StatemineAssetBalance( return queryAssetDetails(chainAsset).minimumBalance } - override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): AccountBalance { val statemineType = chainAsset.requireStatemine() val assetAccount = remoteStorage.query(chain.id) { @@ -68,7 +69,21 @@ class StatemineAssetBalance( ) } - return assetAccount.balance + val frozenBalance = if (assetAccount.isBalanceFrozen) { + assetAccount.balance + } else { + BigInteger.ZERO + } + + return AccountBalance( + free = assetAccount.balance, + reserved = BigInteger.ZERO, + frozen = frozenBalance + ) + } + + override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { + return queryAccountBalance(chain, chainAsset, accountId).free } override suspend fun startSyncingBalance( diff --git a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/utility/NativeAssetBalance.kt b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/utility/NativeAssetBalance.kt index 6312a45a1d..7edad10b9d 100644 --- a/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/utility/NativeAssetBalance.kt +++ b/feature-wallet-impl/src/main/java/io/novafoundation/nova/feature_wallet_impl/data/network/blockchain/assets/balances/utility/NativeAssetBalance.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_wallet_impl.data.network.blockchain.assets.balances.utility import android.util.Log +import io.novafoundation.nova.common.data.network.runtime.binding.AccountBalance import io.novafoundation.nova.common.utils.LOG_TAG import io.novafoundation.nova.common.utils.balances import io.novafoundation.nova.common.utils.decodeValue @@ -64,10 +65,14 @@ class NativeAssetBalance( return runtime.metadata.balances().numberConstant("ExistentialDeposit", runtime) } + override suspend fun queryAccountBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): AccountBalance { + return substrateRemoteSource.getAccountInfo(chain.id, accountId).data + } + override suspend fun queryTotalBalance(chain: Chain, chainAsset: Chain.Asset, accountId: AccountId): BigInteger { - val accountInfo = substrateRemoteSource.getAccountInfo(chain.id, accountId) + val accountData = queryAccountBalance(chain, chainAsset, accountId) - return accountInfo.data.free + accountInfo.data.reserved + return accountData.free + accountData.reserved } override suspend fun startSyncingBalance( From 79a43ec1854c58bb6fb0a157182017b148875253 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 9 Jan 2024 08:18:31 +0100 Subject: [PATCH 083/100] Proxy bugs and improvements Update proxy wiki link Replace text in Settings account title Update proxy type title formatting Fixed bug when after revoke access no wallet is selected in bottom-sheet --- common/build.gradle | 1 + .../nova/common/data/network/AppLinksProvider.kt | 1 + .../nova/common/di/modules/NetworkModule.kt | 1 + common/src/main/res/values/strings.xml | 2 +- .../domain/interfaces/AccountInteractor.kt | 2 ++ .../domain/AccountInteractorImpl.kt | 11 +++++++++++ .../account/common/listing/ProxyFormatter.kt | 2 +- .../DelegatedAccountUpdatesViewModel.kt | 11 +++++++++-- .../di/DelegatedAccountUpdatesModule.kt | 5 ++++- 9 files changed, 31 insertions(+), 5 deletions(-) diff --git a/common/build.gradle b/common/build.gradle index 5f31722bda..d029850d00 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -34,6 +34,7 @@ android { buildConfigField "String", "PARITY_SIGNER_TROUBLESHOOTING", "\"https://docs.novawallet.io/nova-wallet-wiki/welcome-to-nova-wallet/hardware-wallets/parity-signer/troubleshooting\"" buildConfigField "String", "POLKADOT_VAULT_TROUBLESHOOTING", "\"https://docs.novawallet.io/nova-wallet-wiki/welcome-to-nova-wallet/hardware-wallets/polkadot-vault/troubleshooting\"" buildConfigField "String", "NOVA_WALLET_WIKI_BASE", "\"https://docs.novawallet.io/nova-wallet-wiki/welcome-to-nova-wallet/about-nova-wallet\"" + buildConfigField "String", "NOVA_WALLET_WIKI_PROXY", "\"https://docs.novawallet.io/nova-wallet-wiki/wallet-management/delegated-authorities-proxies\"" buildConfigField "String", "LEDGER_BLEUTOOTH_GUIDE", "\"https://support.ledger.com/hc/en-us/articles/360019138694-Set-up-Bluetooth-connection\"" } diff --git a/common/src/main/java/io/novafoundation/nova/common/data/network/AppLinksProvider.kt b/common/src/main/java/io/novafoundation/nova/common/data/network/AppLinksProvider.kt index af94c6036e..fd86d166b2 100644 --- a/common/src/main/java/io/novafoundation/nova/common/data/network/AppLinksProvider.kt +++ b/common/src/main/java/io/novafoundation/nova/common/data/network/AppLinksProvider.kt @@ -21,6 +21,7 @@ class AppLinksProvider( val polkadotVaultTroubleShooting: String, val ledgerBluetoothGuide: String, val wikiBase: String, + val wikiProxy: String, ) { fun getTwitterAccountUrl( diff --git a/common/src/main/java/io/novafoundation/nova/common/di/modules/NetworkModule.kt b/common/src/main/java/io/novafoundation/nova/common/di/modules/NetworkModule.kt index 8d91c07545..396690d256 100644 --- a/common/src/main/java/io/novafoundation/nova/common/di/modules/NetworkModule.kt +++ b/common/src/main/java/io/novafoundation/nova/common/di/modules/NetworkModule.kt @@ -59,6 +59,7 @@ class NetworkModule { rateApp = BuildConfig.RATE_URL, website = BuildConfig.WEBSITE_URL, wikiBase = BuildConfig.NOVA_WALLET_WIKI_BASE, + wikiProxy = BuildConfig.NOVA_WALLET_WIKI_PROXY, github = BuildConfig.GITHUB_URL, email = BuildConfig.EMAIL, youtube = BuildConfig.YOUTUBE_URL, diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 0d0d507f90..431649cf71 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -4,7 +4,7 @@ Delegated account %s doesn’t have enough balance to pay the network fee of %s. Available balance to pay fee: %s Not enough tokens to pay the fee - This account has provided access to perform transactions to the following account: + This account granted access to perform transactions to the following account: Do not show this again diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt index ff33de1c27..106aece206 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt @@ -59,4 +59,6 @@ interface AccountInteractor { suspend fun getChainAddress(metaId: Long, chainId: ChainId): String? suspend fun removeDeactivatedMetaAccounts() + + suspend fun switchToNotDeactivatedAccountIfNeeded() } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt index 9e87a430cd..49985f87f0 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt @@ -6,6 +6,7 @@ import io.novafoundation.nova.core.model.Node import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.Account +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering import io.novafoundation.nova.feature_account_api.domain.model.PreferredCryptoType @@ -190,4 +191,14 @@ class AccountInteractorImpl( } } } + + override suspend fun switchToNotDeactivatedAccountIfNeeded() { + val metaAccount = accountRepository.getSelectedMetaAccount() + if (metaAccount.status == LightMetaAccount.Status.DEACTIVATED) { + val metaAccounts = getMetaAccounts() + if (metaAccounts.isNotEmpty()) { + accountRepository.selectMetaAccount(metaAccounts.first().id) + } + } + } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt index e5aed65682..bff8fb7310 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt @@ -51,7 +51,7 @@ class ProxyFormatter( ProxyAccount.ProxyType.CancelProxy -> resourceManager.getString(R.string.account_proxy_type_cancel_proxy) ProxyAccount.ProxyType.Auction -> resourceManager.getString(R.string.account_proxy_type_auction) ProxyAccount.ProxyType.NominationPools -> resourceManager.getString(R.string.account_proxy_type_nomination_pools) - is ProxyAccount.ProxyType.Other -> type.name.splitCamelCase().joinToString { it.capitalize() } + is ProxyAccount.ProxyType.Other -> type.name } return resourceManager.getString(R.string.proxy_wallet_type, proxyType) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesViewModel.kt index a52f0a0c70..ed6ec01a32 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/DelegatedAccountUpdatesViewModel.kt @@ -7,14 +7,17 @@ import io.novafoundation.nova.common.data.network.AppLinksProvider import io.novafoundation.nova.common.mixin.api.Browserable import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.event +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch class DelegatedAccountUpdatesViewModel( private val delegatedMetaAccountUpdatesListingMixinFactory: DelegatedMetaAccountUpdatesListingMixinFactory, private val accountRouter: AccountRouter, private val appLinksProvider: AppLinksProvider, + private val accountInteractor: AccountInteractor ) : BaseViewModel(), Browserable { private val listingMixin = delegatedMetaAccountUpdatesListingMixinFactory.create(viewModelScope) @@ -24,10 +27,14 @@ class DelegatedAccountUpdatesViewModel( override val openBrowserEvent = MutableLiveData>() fun clickAbout() { - openBrowserEvent.value = appLinksProvider.wikiBase.event() + openBrowserEvent.value = appLinksProvider.wikiProxy.event() } fun clickDone() { - accountRouter.back() + launch { + accountInteractor.switchToNotDeactivatedAccountIfNeeded() + + accountRouter.back() + } } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesModule.kt index 026dab0742..69dbb66ff7 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/delegationUpdates/di/DelegatedAccountUpdatesModule.kt @@ -9,6 +9,7 @@ import dagger.multibindings.IntoMap import io.novafoundation.nova.common.data.network.AppLinksProvider import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory import io.novafoundation.nova.feature_account_impl.presentation.account.list.delegationUpdates.DelegatedAccountUpdatesViewModel @@ -23,11 +24,13 @@ class DelegatedAccountUpdatesModule { delegatedMetaAccountUpdatesListingMixinFactory: DelegatedMetaAccountUpdatesListingMixinFactory, accountRouter: AccountRouter, appLinksProvider: AppLinksProvider, + accountInteractor: AccountInteractor ): ViewModel { return DelegatedAccountUpdatesViewModel( delegatedMetaAccountUpdatesListingMixinFactory, accountRouter, - appLinksProvider + appLinksProvider, + accountInteractor ) } From ccede0cd08d066cd0a1d000c605e41b2e4e573a3 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 9 Jan 2024 08:35:01 +0100 Subject: [PATCH 084/100] Update ProxyFormatter.kt --- .../presentation/account/common/listing/ProxyFormatter.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt index bff8fb7310..7e680940cc 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt @@ -6,10 +6,8 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.append import io.novafoundation.nova.common.utils.appendEnd import io.novafoundation.nova.common.utils.appendSpace -import io.novafoundation.nova.common.utils.capitalize import io.novafoundation.nova.common.utils.colorSpan import io.novafoundation.nova.common.utils.drawableSpan -import io.novafoundation.nova.common.utils.splitCamelCase import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase From a8add4cb0d0d2d6de25fa56ba52f791965846e49 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 9 Jan 2024 08:51:37 +0100 Subject: [PATCH 085/100] Update ProxyFormatter.kt --- .../presentation/account/common/listing/ProxyFormatter.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt index 7e680940cc..d4cc391f81 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/common/listing/ProxyFormatter.kt @@ -6,8 +6,10 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.append import io.novafoundation.nova.common.utils.appendEnd import io.novafoundation.nova.common.utils.appendSpace +import io.novafoundation.nova.common.utils.capitalize import io.novafoundation.nova.common.utils.colorSpan import io.novafoundation.nova.common.utils.drawableSpan +import io.novafoundation.nova.common.utils.splitCamelCase import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase @@ -49,7 +51,7 @@ class ProxyFormatter( ProxyAccount.ProxyType.CancelProxy -> resourceManager.getString(R.string.account_proxy_type_cancel_proxy) ProxyAccount.ProxyType.Auction -> resourceManager.getString(R.string.account_proxy_type_auction) ProxyAccount.ProxyType.NominationPools -> resourceManager.getString(R.string.account_proxy_type_nomination_pools) - is ProxyAccount.ProxyType.Other -> type.name + is ProxyAccount.ProxyType.Other -> type.name.splitCamelCase().joinToString(separator = " ") { it.capitalize() } } return resourceManager.getString(R.string.proxy_wallet_type, proxyType) From 79ac2deb96ce4114e8b44c0d57d9dbd8b4b04f38 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Tue, 9 Jan 2024 09:12:38 +0100 Subject: [PATCH 086/100] Improve account switching logic --- .../novafoundation/nova/core_db/dao/MetaAccountDao.kt | 4 ++++ .../domain/interfaces/AccountRepository.kt | 2 ++ .../data/repository/AccountRepositoryImpl.kt | 4 ++++ .../data/repository/datasource/AccountDataSource.kt | 2 ++ .../repository/datasource/AccountDataSourceImpl.kt | 5 +++++ .../domain/AccountInteractorImpl.kt | 10 +++++----- 6 files changed, 22 insertions(+), 5 deletions(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index 7f982caeac..a28079bb72 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -108,6 +108,10 @@ interface MetaAccountDao { @Transaction suspend fun getJoinedMetaAccountsInfo(): List + @Query("SELECT * FROM meta_accounts WHERE status = :status") + @Transaction + suspend fun getMetaAccountsInfoByStatus(status: MetaAccountLocal.Status): List + @Query("SELECT id FROM meta_accounts WHERE status = :status") suspend fun getMetaAccountIdsByStatus(status: MetaAccountLocal.Status): List diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index f1bc438aef..c3840b876e 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -125,4 +125,6 @@ interface AccountRepository { suspend fun isAccountExists(accountId: AccountId, chainId: ChainId): Boolean suspend fun removeDeactivatedMetaAccounts() + + suspend fun getActiveMetaAccounts(): List } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index ade4b5d179..cb8749d1ef 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -257,6 +257,10 @@ class AccountRepositoryImpl( accountDataSource.removeDeactivatedMetaAccounts() } + override suspend fun getActiveMetaAccounts(): List { + return accountDataSource.getActiveMetaAccounts() + } + override fun nodesFlow(): Flow> { return nodeDao.nodesFlow() .mapList { mapNodeLocalToNode(it) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 7cb2ec80b2..e29557c232 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -97,4 +97,6 @@ interface AccountDataSource : SecretStoreV1 { suspend fun hasMetaAccounts(): Boolean fun removeDeactivatedMetaAccounts() + + suspend fun getActiveMetaAccounts(): List } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 4e996e62da..96056ebe54 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -146,6 +146,11 @@ class AccountDataSourceImpl( return metaAccountDao.getJoinedMetaAccountsInfo().map(::mapMetaAccountLocalToMetaAccount) } + override suspend fun getActiveMetaAccounts(): List { + return metaAccountDao.getMetaAccountsInfoByStatus(MetaAccountLocal.Status.ACTIVE) + .map(::mapMetaAccountLocalToMetaAccount) + } + override suspend fun allLightMetaAccounts(): List { return metaAccountDao.getMetaAccounts().map(::mapMetaAccountLocalToLightMetaAccount) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt index 49985f87f0..2c97495c97 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt @@ -194,11 +194,11 @@ class AccountInteractorImpl( override suspend fun switchToNotDeactivatedAccountIfNeeded() { val metaAccount = accountRepository.getSelectedMetaAccount() - if (metaAccount.status == LightMetaAccount.Status.DEACTIVATED) { - val metaAccounts = getMetaAccounts() - if (metaAccounts.isNotEmpty()) { - accountRepository.selectMetaAccount(metaAccounts.first().id) - } + if (metaAccount.status != LightMetaAccount.Status.DEACTIVATED) return + + val metaAccounts = accountRepository.getActiveMetaAccounts() + if (metaAccounts.isNotEmpty()) { + accountRepository.selectMetaAccount(metaAccounts.first().id) } } } From db49de00daa7c5a99b58d10049f4b9d3cf595e3d Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Tue, 9 Jan 2024 15:25:41 +0700 Subject: [PATCH 087/100] Fix/proxy ledger (#1313) * Fix proxy signing with ledger * Code style --- .../data/signer/SeparateFlowSignerState.kt | 9 ++++++++ .../di/AccountFeatureApi.kt | 7 +++--- .../data/signer/SeparateFlowSigner.kt | 11 ++++++---- .../data/signer/ledger/LedgerSigner.kt | 7 +++--- .../paritySigner/PolkadotVaultSigner.kt | 10 ++++----- .../di/modules/ParitySignerModule.kt | 8 +++---- .../di/modules/signers/SignersModule.kt | 9 ++++---- .../scan/ScanSignParitySignerViewModel.kt | 7 +++--- .../scan/di/ScanSignParitySignerModule.kt | 5 ++--- .../show/ShowSignParitySignerViewModel.kt | 12 +++++----- .../show/di/ShowSignParitySignerModule.kt | 4 ++-- .../di/LedgerFeatureDependencies.kt | 5 ++--- .../account/sign/SignLedgerInteractor.kt | 22 +++++++++++-------- .../account/sign/SignLedgerViewModel.kt | 18 +++++++-------- .../account/sign/di/SignLedgerModule.kt | 18 +++++---------- 15 files changed, 76 insertions(+), 76 deletions(-) create mode 100644 feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SeparateFlowSignerState.kt diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SeparateFlowSignerState.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SeparateFlowSignerState.kt new file mode 100644 index 0000000000..2f5928571e --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/signer/SeparateFlowSignerState.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.feature_account_api.data.signer + +import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic + +typealias SigningSharedState = MutableSharedState + +class SeparateFlowSignerState(val extrinsic: SignerPayloadExtrinsic, val metaAccount: MetaAccount) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt index 72ca04deb9..b23085de94 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/di/AccountFeatureApi.kt @@ -1,8 +1,7 @@ package io.novafoundation.nova.feature_account_api.di -import io.novafoundation.nova.common.sequrity.biometry.BiometricServiceFactory import io.novafoundation.nova.common.sequrity.TwoFactorVerificationExecutor -import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.common.sequrity.biometry.BiometricServiceFactory import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService @@ -10,6 +9,7 @@ import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExt import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity @@ -29,7 +29,6 @@ import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInp import io.novafoundation.nova.feature_account_api.presenatation.mixin.identity.IdentityMixin import io.novafoundation.nova.feature_account_api.presenatation.mixin.importType.ImportTypeChooserMixin import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletMixin -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic interface AccountFeatureApi { @@ -67,7 +66,7 @@ interface AccountFeatureApi { val watchOnlyMissingKeysPresenter: WatchOnlyMissingKeysPresenter - val signSharedState: MutableSharedState + val signSharedState: SigningSharedState val onChainIdentityRepository: OnChainIdentityRepository diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt index fcb1ff9a04..e8d3a5eee0 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/SeparateFlowSigner.kt @@ -1,7 +1,8 @@ package io.novafoundation.nova.feature_account_impl.data.signer import io.novafoundation.nova.common.base.errors.SigningCancelledException -import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.data.signer.SeparateFlowSignerState +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenCommunicator import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenRequester @@ -13,13 +14,15 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext abstract class SeparateFlowSigner( - private val signingSharedState: MutableSharedState, + private val signingSharedState: SigningSharedState, private val signFlowRequester: SignInterScreenRequester, - metaAccount: MetaAccount + private val metaAccount: MetaAccount ) : LeafSigner(metaAccount) { override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { - signingSharedState.set(payloadExtrinsic) + val payload = SeparateFlowSignerState(payloadExtrinsic, metaAccount) + + signingSharedState.set(payload) val result = withContext(Dispatchers.Main) { try { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt index 785c9d79df..e85beeb8c3 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/ledger/LedgerSigner.kt @@ -2,18 +2,17 @@ package io.novafoundation.nova.feature_account_impl.data.signer.ledger import io.novafoundation.nova.common.base.errors.SigningCancelledException import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenRequester import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.data.signer.SeparateFlowSigner import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignedRaw -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw class LedgerSignerFactory( - private val signingSharedState: MutableSharedState, + private val signingSharedState: SigningSharedState, private val signFlowRequester: SignInterScreenRequester, private val resourceManager: ResourceManager, private val messageSigningNotSupported: SigningNotSupportedPresentable @@ -32,7 +31,7 @@ class LedgerSignerFactory( class LedgerSigner( metaAccount: MetaAccount, - signingSharedState: MutableSharedState, + signingSharedState: SigningSharedState, signFlowRequester: SignInterScreenRequester, private val resourceManager: ResourceManager, private val messageSigningNotSupported: SigningNotSupportedPresentable diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt index b2cb427b95..08cae450ac 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/paritySigner/PolkadotVaultSigner.kt @@ -2,7 +2,7 @@ package io.novafoundation.nova.feature_account_impl.data.signer.paritySigner import io.novafoundation.nova.common.base.errors.SigningCancelledException import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.MutableSharedState +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.PolkadotVaultVariant import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider @@ -16,7 +16,7 @@ import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtr import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadRaw class PolkadotVaultVariantSignerFactory( - private val signingSharedState: MutableSharedState, + private val signingSharedState: SigningSharedState, private val signFlowRequester: PolkadotVaultVariantSignCommunicator, private val resourceManager: ResourceManager, private val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, @@ -47,7 +47,7 @@ class PolkadotVaultVariantSignerFactory( } abstract class PolkadotVaultVariantSigner( - signingSharedState: MutableSharedState, + signingSharedState: SigningSharedState, metaAccount: MetaAccount, private val signFlowRequester: PolkadotVaultVariantSignCommunicator, private val resourceManager: ResourceManager, @@ -77,7 +77,7 @@ abstract class PolkadotVaultVariantSigner( } class ParitySignerSigner( - signingSharedState: MutableSharedState, + signingSharedState: SigningSharedState, metaAccount: MetaAccount, signFlowRequester: PolkadotVaultVariantSignCommunicator, resourceManager: ResourceManager, @@ -94,7 +94,7 @@ class ParitySignerSigner( ) class PolkadotVaultSigner( - signingSharedState: MutableSharedState, + signingSharedState: SigningSharedState, metaAccount: MetaAccount, signFlowRequester: PolkadotVaultVariantSignCommunicator, resourceManager: ResourceManager, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt index fab7cee9e4..6b65e50d16 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/ParitySignerModule.kt @@ -5,12 +5,12 @@ import dagger.Provides import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.common.utils.SharedState +import io.novafoundation.nova.feature_account_api.data.signer.SeparateFlowSignerState +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.common.QrCodeExpiredPresentableFactory -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @Module class ParitySignerModule { @@ -18,8 +18,8 @@ class ParitySignerModule { @Provides @FeatureScope fun provideReadOnlySharedState( - mutableSharedState: MutableSharedState - ): SharedState = mutableSharedState + mutableSharedState: SigningSharedState + ): SharedState = mutableSharedState @Provides @FeatureScope diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt index 901d364e5b..a08d1d33eb 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/signers/SignersModule.kt @@ -7,8 +7,8 @@ import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.TwoFactorVerificationService import io.novafoundation.nova.common.utils.DefaultMutableSharedState -import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter @@ -23,14 +23,13 @@ import io.novafoundation.nova.feature_account_impl.data.signer.secrets.SecretsSi import io.novafoundation.nova.feature_account_impl.data.signer.watchOnly.WatchOnlySignerFactory import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notSupported.SigningNotSupportedPresentable import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @Module(includes = [ProxiedSignerModule::class]) class SignersModule { @Provides @FeatureScope - fun provideSignSharedState(): MutableSharedState = DefaultMutableSharedState() + fun provideSignSharedState(): SigningSharedState = DefaultMutableSharedState() @Provides @FeatureScope @@ -55,7 +54,7 @@ class SignersModule { @Provides @FeatureScope fun providePolkadotVaultVariantSignerFactory( - signingSharedState: MutableSharedState, + signingSharedState: SigningSharedState, communicator: PolkadotVaultVariantSignCommunicator, resourceManager: ResourceManager, polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider, @@ -71,7 +70,7 @@ class SignersModule { @Provides @FeatureScope fun provideLedgerSignerFactory( - signingSharedState: MutableSharedState, + signingSharedState: SigningSharedState, communicator: LedgerSignCommunicator, resourceManager: ResourceManager, signingNotSupportedPresentable: SigningNotSupportedPresentable diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/scan/ScanSignParitySignerViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/scan/ScanSignParitySignerViewModel.kt index 61b9263c7e..6de400028a 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/scan/ScanSignParitySignerViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/scan/ScanSignParitySignerViewModel.kt @@ -5,9 +5,9 @@ import io.novafoundation.nova.common.mixin.actionAwaitable.awaitAction import io.novafoundation.nova.common.mixin.actionAwaitable.confirmingAction import io.novafoundation.nova.common.presentation.scan.ScanQrViewModel import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.SharedState import io.novafoundation.nova.common.utils.getOrThrow import io.novafoundation.nova.common.utils.permissions.PermissionsAsker +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.formatWithPolkadotVaultLabel import io.novafoundation.nova.feature_account_api.presenatation.sign.signed import io.novafoundation.nova.feature_account_impl.R @@ -18,7 +18,6 @@ import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sig import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.scan.model.ScanSignParitySignerPayload import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.scan.model.mapValidityPeriodFromParcel import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper.Sr25519 -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch @@ -26,7 +25,7 @@ class ScanSignParitySignerViewModel( private val router: AccountRouter, permissionsAsker: PermissionsAsker.Presentation, private val interactor: ScanSignParitySignerInteractor, - private val signSharedState: SharedState, + private val signSharedState: SigningSharedState, private val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, private val responder: PolkadotVaultVariantSignCommunicator, private val payload: ScanSignParitySignerPayload, @@ -55,7 +54,7 @@ class ScanSignParitySignerViewModel( } override suspend fun scanned(result: String) { - interactor.encodeAndVerifySignature(signSharedState.getOrThrow(), result) + interactor.encodeAndVerifySignature(signSharedState.getOrThrow().extrinsic, result) .onSuccess(::respondResult) .onFailure { invalidQrConfirmation.awaitAction() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/scan/di/ScanSignParitySignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/scan/di/ScanSignParitySignerModule.kt index 5a999f9e7d..addb0dcfa1 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/scan/di/ScanSignParitySignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/scan/di/ScanSignParitySignerModule.kt @@ -10,9 +10,9 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.common.utils.permissions.PermissionsAsker import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator import io.novafoundation.nova.feature_account_impl.domain.paritySigner.sign.scan.RealScanSignParitySignerInteractor import io.novafoundation.nova.feature_account_impl.domain.paritySigner.sign.scan.ScanSignParitySignerInteractor @@ -20,7 +20,6 @@ import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.common.QrCodeExpiredPresentableFactory import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.scan.ScanSignParitySignerViewModel import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.scan.model.ScanSignParitySignerPayload -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @Module(includes = [ViewModelModule::class]) class ScanSignParitySignerModule { @@ -42,7 +41,7 @@ class ScanSignParitySignerModule { router: AccountRouter, permissionsAsker: PermissionsAsker.Presentation, interactor: ScanSignParitySignerInteractor, - signSharedState: MutableSharedState, + signSharedState: SigningSharedState, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, communicator: PolkadotVaultVariantSignCommunicator, payload: ScanSignParitySignerPayload, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/show/ShowSignParitySignerViewModel.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/show/ShowSignParitySignerViewModel.kt index 5d2e6e6df7..a38f88628b 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/show/ShowSignParitySignerViewModel.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/show/ShowSignParitySignerViewModel.kt @@ -12,6 +12,7 @@ import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.common.utils.getOrThrow import io.novafoundation.nova.common.utils.mediatorLiveData import io.novafoundation.nova.common.utils.updateFrom +import io.novafoundation.nova.feature_account_api.data.signer.SeparateFlowSignerState import io.novafoundation.nova.feature_account_api.presenatation.account.AddressDisplayUseCase import io.novafoundation.nova.feature_account_api.presenatation.account.icon.createAccountAddressModel import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider @@ -28,7 +29,6 @@ import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sig import io.novafoundation.nova.runtime.extrinsic.ExtrinsicValidityUseCase import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.fearless_utils.extensions.toHexString -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.genesisHash import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map @@ -37,7 +37,7 @@ import kotlinx.coroutines.launch class ShowSignParitySignerViewModel( private val router: AccountRouter, private val interactor: ShowSignParitySignerInteractor, - private val signSharedState: SharedState, + private val signSharedState: SharedState, private val qrCodeGenerator: QrCodeGenerator, private val responder: PolkadotVaultVariantSignCommunicator, private val payload: ShowSignParitySignerPayload, @@ -59,7 +59,7 @@ class ShowSignParitySignerViewModel( val chain = flowOf { val signPayload = signSharedState.getOrThrow() - val chainId = signPayload.genesisHash.toHexString() + val chainId = signPayload.extrinsic.genesisHash.toHexString() chainRegistry.getChain(chainId) }.shareInBackground() @@ -67,7 +67,7 @@ class ShowSignParitySignerViewModel( val qrCodeSequence = flowOf { val signPayload = signSharedState.getOrThrow() - val frames = interactor.qrCodeContent(signPayload).frames + val frames = interactor.qrCodeContent(signPayload.extrinsic).frames frames.map { qrCodeGenerator.generateQrBitmap(it) } .cycleMultiple() @@ -76,11 +76,11 @@ class ShowSignParitySignerViewModel( val addressModel = chain.map { chain -> val signPayload = signSharedState.getOrThrow() - addressIconGenerator.createAccountAddressModel(chain, signPayload.accountId, addressDisplayUseCase) + addressIconGenerator.createAccountAddressModel(chain, signPayload.extrinsic.accountId, addressDisplayUseCase) }.shareInBackground() val validityPeriod = flowOf { - extrinsicValidityUseCase.extrinsicValidityPeriod(signSharedState.getOrThrow()) + extrinsicValidityUseCase.extrinsicValidityPeriod(signSharedState.getOrThrow().extrinsic) }.shareInBackground() val title = resourceManager.formatWithPolkadotVaultLabel(R.string.account_parity_signer_sign_title, payload.polkadotVaultVariant) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/show/di/ShowSignParitySignerModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/show/di/ShowSignParitySignerModule.kt index 7345465164..2a3de95543 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/show/di/ShowSignParitySignerModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/paritySigner/sign/show/di/ShowSignParitySignerModule.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.QrCodeGenerator import io.novafoundation.nova.common.utils.SharedState +import io.novafoundation.nova.feature_account_api.data.signer.SeparateFlowSignerState import io.novafoundation.nova.feature_account_api.presenatation.account.AddressDisplayUseCase import io.novafoundation.nova.feature_account_api.presenatation.account.polkadotVault.config.PolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions @@ -25,7 +26,6 @@ import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sig import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.sign.show.ShowSignParitySignerViewModel import io.novafoundation.nova.runtime.extrinsic.ExtrinsicValidityUseCase import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @Module(includes = [ViewModelModule::class]) class ShowSignParitySignerModule { @@ -39,7 +39,7 @@ class ShowSignParitySignerModule { @ViewModelKey(ShowSignParitySignerViewModel::class) fun provideViewModel( interactor: ShowSignParitySignerInteractor, - signSharedState: SharedState, + signSharedState: SharedState, qrCodeGenerator: QrCodeGenerator, communicator: PolkadotVaultVariantSignCommunicator, payload: ShowSignParitySignerPayload, diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureDependencies.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureDependencies.kt index 0e6f3726a9..09f9f867b4 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureDependencies.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/di/LedgerFeatureDependencies.kt @@ -7,12 +7,12 @@ import io.novafoundation.nova.common.data.secrets.v2.SecretStoreV2 import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin import io.novafoundation.nova.common.resources.ContextManager import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager import io.novafoundation.nova.common.utils.location.LocationManager import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase @@ -20,7 +20,6 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.assets.A import io.novafoundation.nova.feature_wallet_api.domain.interfaces.TokenRepository import io.novafoundation.nova.runtime.extrinsic.ExtrinsicValidityUseCase import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic interface LedgerFeatureDependencies { @@ -56,7 +55,7 @@ interface LedgerFeatureDependencies { val secretStoreV2: SecretStoreV2 - val signSharedState: MutableSharedState + val signSharedState: SigningSharedState val extrinsicValidityUseCase: ExtrinsicValidityUseCase diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/sign/SignLedgerInteractor.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/sign/SignLedgerInteractor.kt index 8cf77b62d6..555a3ddc42 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/sign/SignLedgerInteractor.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/domain/account/sign/SignLedgerInteractor.kt @@ -1,32 +1,36 @@ package io.novafoundation.nova.feature_ledger_impl.domain.account.sign import io.novafoundation.nova.common.utils.chainId -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.data.signer.SeparateFlowSignerState import io.novafoundation.nova.feature_account_api.domain.model.publicKeyIn import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.fearless_utils.encrypt.SignatureVerifier import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper import jp.co.soramitsu.fearless_utils.encrypt.Signer.MessageHashing -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.encodedSignaturePayload interface SignLedgerInteractor { - suspend fun verifySignature(payload: SignerPayloadExtrinsic, signature: SignatureWrapper): Boolean + suspend fun verifySignature( + payload: SeparateFlowSignerState, + signature: SignatureWrapper + ): Boolean } class RealSignLedgerInteractor( - private val metaAccountRepository: AccountRepository, private val chainRegistry: ChainRegistry, ) : SignLedgerInteractor { - override suspend fun verifySignature(payload: SignerPayloadExtrinsic, signature: SignatureWrapper): Boolean = runCatching { - val payloadBytes = payload.encodedSignaturePayload(hashBigPayloads = true) - val metaAccount = metaAccountRepository.getSelectedMetaAccount() - val chainId = payload.chainId + override suspend fun verifySignature( + payload: SeparateFlowSignerState, + signature: SignatureWrapper + ): Boolean = runCatching { + val extrinsic = payload.extrinsic + val payloadBytes = extrinsic.encodedSignaturePayload(hashBigPayloads = true) + val chainId = extrinsic.chainId val chain = chainRegistry.getChain(chainId) - val publicKey = metaAccount.publicKeyIn(chain) ?: throw IllegalStateException("No public key for chain $chainId") + val publicKey = payload.metaAccount.publicKeyIn(chain) ?: throw IllegalStateException("No public key for chain $chainId") SignatureVerifier.verify(signature, MessageHashing.SUBSTRATE, payloadBytes, publicKey) }.getOrDefault(false) diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/sign/SignLedgerViewModel.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/sign/SignLedgerViewModel.kt index 49a4a83439..5ce750c37a 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/sign/SignLedgerViewModel.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/sign/SignLedgerViewModel.kt @@ -1,7 +1,6 @@ package io.novafoundation.nova.feature_ledger_impl.presentation.account.sign import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.SharedState import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager import io.novafoundation.nova.common.utils.event import io.novafoundation.nova.common.utils.flowOf @@ -9,7 +8,7 @@ import io.novafoundation.nova.common.utils.getOrThrow import io.novafoundation.nova.common.utils.invoke import io.novafoundation.nova.common.utils.location.LocationManager import io.novafoundation.nova.common.utils.permissions.PermissionsAsker -import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenCommunicator import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenResponder import io.novafoundation.nova.feature_account_api.presenatation.sign.cancelled @@ -30,7 +29,6 @@ import io.novafoundation.nova.runtime.extrinsic.closeToExpire import io.novafoundation.nova.runtime.extrinsic.remainingTime import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import jp.co.soramitsu.fearless_utils.encrypt.SignatureWrapper -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic import kotlinx.coroutines.Deferred import kotlinx.coroutines.async import kotlinx.coroutines.delay @@ -51,9 +49,8 @@ class SignLedgerViewModel( private val selectLedgerPayload: SelectLedgerPayload, private val router: LedgerRouter, private val resourceManager: ResourceManager, - private val signPayloadState: SharedState, + private val signPayloadState: SigningSharedState, private val extrinsicValidityUseCase: ExtrinsicValidityUseCase, - private val selectedAccountUseCase: SelectedAccountUseCase, private val request: SignInterScreenCommunicator.Request, private val responder: SignInterScreenResponder, private val interactor: SignLedgerInteractor, @@ -74,7 +71,7 @@ class SignLedgerViewModel( ) { private val validityPeriod = flowOf { - extrinsicValidityUseCase.extrinsicValidityPeriod(signPayloadState.getOrThrow()) + extrinsicValidityUseCase.extrinsicValidityPeriod(signPayloadState.getOrThrow().extrinsic) }.shareInBackground() private var signingJob: Deferred? = null @@ -105,6 +102,7 @@ class SignLedgerViewModel( override suspend fun verifyConnection(device: LedgerDevice) { val validityPeriod = validityPeriod.first() + val signState = signPayloadState.getOrThrow() ledgerMessageCommands.value = LedgerMessageCommand.Show.Info( title = resourceManager.getString(R.string.ledger_review_approve_title), @@ -118,21 +116,21 @@ class SignLedgerViewModel( ) ).event() - val selectedMetaAccount = selectedAccountUseCase.getSelectedMetaAccount() + val signingMetaAccount = signState.metaAccount signingJob?.cancel() signingJob = async { substrateApplication.getSignature( device = device, - metaId = selectedMetaAccount.id, + metaId = signingMetaAccount.id, chainId = selectLedgerPayload.chainId, - payload = signPayloadState.getOrThrow() + payload = signState.extrinsic ) } val signature = signingJob!!.await() - if (interactor.verifySignature(signPayloadState.getOrThrow(), signature)) { + if (interactor.verifySignature(signState, signature)) { responder.respond(request.signed(signature)) hideBottomSheet() router.finishSignFlow() diff --git a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/sign/di/SignLedgerModule.kt b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/sign/di/SignLedgerModule.kt index a8ae0fd1a2..b3f2e944f1 100644 --- a/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/sign/di/SignLedgerModule.kt +++ b/feature-ledger-impl/src/main/java/io/novafoundation/nova/feature_ledger_impl/presentation/account/sign/di/SignLedgerModule.kt @@ -10,15 +10,13 @@ import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.common.di.viewmodel.ViewModelKey import io.novafoundation.nova.common.di.viewmodel.ViewModelModule import io.novafoundation.nova.common.resources.ResourceManager -import io.novafoundation.nova.common.utils.MutableSharedState import io.novafoundation.nova.common.utils.bluetooth.BluetoothManager import io.novafoundation.nova.common.utils.chainId import io.novafoundation.nova.common.utils.getOrThrow import io.novafoundation.nova.common.utils.location.LocationManager import io.novafoundation.nova.common.utils.permissions.PermissionsAsker import io.novafoundation.nova.common.utils.permissions.PermissionsAskerFactory -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository -import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase +import io.novafoundation.nova.feature_account_api.data.signer.SigningSharedState import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_api.presenatation.sign.SignInterScreenCommunicator import io.novafoundation.nova.feature_ledger_api.sdk.application.substrate.SubstrateLedgerApplication @@ -30,17 +28,13 @@ import io.novafoundation.nova.feature_ledger_impl.presentation.account.common.se import io.novafoundation.nova.feature_ledger_impl.presentation.account.sign.SignLedgerViewModel import io.novafoundation.nova.runtime.extrinsic.ExtrinsicValidityUseCase import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry -import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic @Module(includes = [ViewModelModule::class]) class SignLedgerModule { @Provides @ScreenScope - fun provideInteractor( - accountRepository: AccountRepository, - chainRegistry: ChainRegistry, - ): SignLedgerInteractor = RealSignLedgerInteractor(accountRepository, chainRegistry) + fun provideInteractor(chainRegistry: ChainRegistry): SignLedgerInteractor = RealSignLedgerInteractor(chainRegistry) @Provides @ScreenScope @@ -53,9 +47,9 @@ class SignLedgerModule { @Provides @ScreenScope fun provideSelectLedgerPayload( - signPayloadState: MutableSharedState, + signPayloadState: SigningSharedState, ): SelectLedgerPayload = SelectLedgerPayload( - chainId = signPayloadState.getOrThrow().chainId + chainId = signPayloadState.getOrThrow().extrinsic.chainId ) @Provides @@ -71,9 +65,8 @@ class SignLedgerModule { router: LedgerRouter, resourceManager: ResourceManager, chainRegistry: ChainRegistry, - signPayloadState: MutableSharedState, + signPayloadState: SigningSharedState, extrinsicValidityUseCase: ExtrinsicValidityUseCase, - selectedAccountUseCase: SelectedAccountUseCase, request: SignInterScreenCommunicator.Request, interactor: SignLedgerInteractor, responder: LedgerSignCommunicator, @@ -90,7 +83,6 @@ class SignLedgerModule { chainRegistry = chainRegistry, signPayloadState = signPayloadState, extrinsicValidityUseCase = extrinsicValidityUseCase, - selectedAccountUseCase = selectedAccountUseCase, request = request, responder = responder, interactor = interactor From b911d1ac6e09ba764778f01e7b21906999ad3a0b Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:01:06 +0700 Subject: [PATCH 088/100] Do not sync WO proxies in production (#1315) --- .../data/proxy/RealProxySyncService.kt | 8 ++-- .../di/AccountFeatureModule.kt | 44 ++++++++++--------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 17f5853a56..5f4da12278 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -36,7 +36,8 @@ class RealProxySyncService( private val identityProvider: IdentityProvider, private val metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, private val proxiedAddAccountRepository: ProxiedAddAccountRepository, - private val rootScope: RootScope + private val rootScope: RootScope, + private val shouldSyncWatchOnlyProxies: Boolean ) : ProxySyncService { override fun startSyncing() { @@ -116,8 +117,9 @@ class RealProxySyncService( LightMetaAccount.Type.SECRETS, LightMetaAccount.Type.PARITY_SIGNER, LightMetaAccount.Type.LEDGER, - LightMetaAccount.Type.POLKADOT_VAULT, - LightMetaAccount.Type.WATCH_ONLY -> true + LightMetaAccount.Type.POLKADOT_VAULT -> true + + LightMetaAccount.Type.WATCH_ONLY -> shouldSyncWatchOnlyProxies LightMetaAccount.Type.PROXIED -> false } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt index 4600ff7711..1741d43d12 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/AccountFeatureModule.kt @@ -24,7 +24,6 @@ import io.novafoundation.nova.core.model.CryptoType import io.novafoundation.nova.core_db.dao.AccountDao import io.novafoundation.nova.core_db.dao.MetaAccountDao import io.novafoundation.nova.core_db.dao.NodeDao -import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.EvmTransactionService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.proxy.MetaAccountsUpdatesRegistry @@ -32,6 +31,9 @@ import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService import io.novafoundation.nova.feature_account_api.data.repository.OnChainIdentityRepository import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider +import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults +import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider +import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor @@ -49,15 +51,21 @@ import io.novafoundation.nova.feature_account_api.presenatation.mixin.importType import io.novafoundation.nova.feature_account_api.presenatation.mixin.importType.ImportTypeChooserProvider import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletMixin +import io.novafoundation.nova.feature_account_impl.BuildConfig import io.novafoundation.nova.feature_account_impl.RealBiometricServiceFactory import io.novafoundation.nova.feature_account_impl.data.ethereum.transaction.RealEvmTransactionService import io.novafoundation.nova.feature_account_impl.data.extrinsic.RealExtrinsicService import io.novafoundation.nova.feature_account_impl.data.network.blockchain.AccountSubstrateSource import io.novafoundation.nova.feature_account_impl.data.network.blockchain.AccountSubstrateSourceImpl +import io.novafoundation.nova.feature_account_impl.data.proxy.RealMetaAccountsUpdatesRegistry import io.novafoundation.nova.feature_account_impl.data.proxy.RealProxySyncService import io.novafoundation.nova.feature_account_impl.data.repository.AccountRepositoryImpl import io.novafoundation.nova.feature_account_impl.data.repository.RealOnChainIdentityRepository import io.novafoundation.nova.feature_account_impl.data.repository.RealProxyRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.JsonAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.MnemonicAddAccountRepository +import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.SeedAddAccountRepository import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSource import io.novafoundation.nova.feature_account_impl.data.repository.datasource.AccountDataSourceImpl import io.novafoundation.nova.feature_account_impl.data.repository.datasource.migration.AccountDataMigration @@ -65,22 +73,14 @@ import io.novafoundation.nova.feature_account_impl.data.secrets.AccountSecretsFa import io.novafoundation.nova.feature_account_impl.di.modules.AdvancedEncryptionStoreModule import io.novafoundation.nova.feature_account_impl.di.modules.IdentityProviderModule import io.novafoundation.nova.feature_account_impl.di.modules.ParitySignerModule -import io.novafoundation.nova.feature_account_impl.di.modules.signers.SignersModule +import io.novafoundation.nova.feature_account_impl.di.modules.ProxySigningModule import io.novafoundation.nova.feature_account_impl.di.modules.WatchOnlyModule +import io.novafoundation.nova.feature_account_impl.di.modules.signers.SignersModule import io.novafoundation.nova.feature_account_impl.domain.AccountInteractorImpl import io.novafoundation.nova.feature_account_impl.domain.MetaAccountGroupingInteractorImpl import io.novafoundation.nova.feature_account_impl.domain.NodeHostValidator import io.novafoundation.nova.feature_account_impl.domain.account.add.AddAccountInteractor import io.novafoundation.nova.feature_account_impl.domain.account.advancedEncryption.AdvancedEncryptionInteractor -import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults -import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider -import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChainIdentity -import io.novafoundation.nova.feature_account_impl.data.proxy.RealMetaAccountsUpdatesRegistry -import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository -import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.JsonAddAccountRepository -import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.MnemonicAddAccountRepository -import io.novafoundation.nova.feature_account_impl.data.repository.addAccount.secrets.SeedAddAccountRepository -import io.novafoundation.nova.feature_account_impl.di.modules.ProxySigningModule import io.novafoundation.nova.feature_account_impl.domain.account.details.WalletDetailsInteractor import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.DelegatedMetaAccountUpdatesListingMixinFactory @@ -98,6 +98,7 @@ import io.novafoundation.nova.feature_account_impl.presentation.mixin.selectWall import io.novafoundation.nova.feature_account_impl.presentation.paritySigner.config.RealPolkadotVaultVariantConfigProvider import io.novafoundation.nova.feature_currency_api.domain.interfaces.CurrencyRepository import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE +import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory import io.novafoundation.nova.runtime.extrinsic.multi.ExtrinsicSplitter import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -105,11 +106,11 @@ import io.novafoundation.nova.runtime.multiNetwork.qr.MultiChainQrSharingFactory import io.novafoundation.nova.runtime.network.rpc.RpcCalls import io.novafoundation.nova.runtime.storage.source.StorageDataSource import io.novafoundation.nova.web3names.domain.networking.Web3NamesInteractor +import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption import jp.co.soramitsu.fearless_utils.encrypt.json.JsonSeedDecoder import jp.co.soramitsu.fearless_utils.encrypt.json.JsonSeedEncoder -import javax.inject.Named -import jp.co.soramitsu.fearless_utils.encrypt.MultiChainEncryption import jp.co.soramitsu.fearless_utils.encrypt.junction.BIP32JunctionDecoder +import javax.inject.Named @Module( includes = [ @@ -149,14 +150,15 @@ class AccountFeatureModule { proxiedAddAccountRepository: ProxiedAddAccountRepository, rootScope: RootScope ): ProxySyncService = RealProxySyncService( - chainRegistry, - proxyRepository, - accounRepository, - metaAccountDao, - identityProvider, - metaAccountsUpdatesRegistry, - proxiedAddAccountRepository, - rootScope + chainRegistry = chainRegistry, + proxyRepository = proxyRepository, + accountRepository = accounRepository, + accountDao = metaAccountDao, + identityProvider = identityProvider, + metaAccountsUpdatesRegistry = metaAccountsUpdatesRegistry, + proxiedAddAccountRepository = proxiedAddAccountRepository, + rootScope = rootScope, + shouldSyncWatchOnlyProxies = BuildConfig.DEBUG ) @Provides From 662a10e3301a00764a5cd929d83fe1f203d76a95 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:27:05 +0700 Subject: [PATCH 089/100] Localization (#1316) * Localization * Fixes --- common/src/main/res/values-ru/strings.xml | 26 ++++++++++++++++++++++- common/src/main/res/values/strings.xml | 4 ++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index 5dc69c1011..ee08eee13b 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -109,6 +109,15 @@ Перейдите на вкладку «Наборы ключей». Выберите свой набор ключей, сеть, а затем учетную запись, которую вы хотите добавить в Nova Wallet Polkadot Vault предоставит вам QR-код для сканирования Приватный ключ + Делегировано вам (проксированные) + Любые действия + Аукционы + Отмена прокси + Демократия + Идентификация личности + Пулы номинаций + Не переводы + Стейкинг У меня уже есть аккаунт 64 шест. символа Выберите аппаратный кошелёк @@ -232,6 +241,7 @@ Отключен Отключить Готово + Больше не показывать это Редактировать %s (и еще %s) Выберите приложение для работы с почтой @@ -482,6 +492,8 @@ Поиск по адресу или имени Недопустимый формат адреса. Убедитесь, что адрес принадлежит правильной сети результаты поиска: %d + Nova Wallet автоматически добавляет делегированные аккаунты (Прокси) в отдельную категорию для вас. Вы всегда можете управлять кошельками в Настройках. + Обновление делегированных аккаунтов Добавить делегацию Голоса за все время Делегат @@ -526,6 +538,7 @@ ECDSA ed25519 (альтернативный) Edwards + Недостаточно токенов для оплаты комиссии Контракт Вызов контракта Функция @@ -536,7 +549,6 @@ Сеть: %s\nМнемоника: %s Подождите, пока будет рассчитана комиссия Расчет комиссии в процессе - Выберите токен для получения Детали обмена Макс: Вы платите @@ -597,6 +609,8 @@ Подключение… Коллекция Создатель + %s за %s + %s единиц из %s #%s Издание из %s Неограниченная серия Владелец @@ -653,6 +667,16 @@ Подтверждение с PIN Безопасный режим Настройки + Этот аккаунт предоставил доступ для выполнения транзакций следующему аккаунту: + Более не действительны + Что такое прокси? + Делегированный аккаунт %s не имеет достаточного баланса для оплаты сетевой комиссии %s. Доступный баланс для оплаты комиссии: %s + Проксированные кошельки не поддерживают подпись произвольных сообщений - только транзакций + %1$s делегировал %2$s только для %3$s + Упс! Недостаточно разрешений + Транзакция будет инициирована %s как делегированным аккаунтом. Сетевая комиссия будет оплачена делегированным аккаунтом. + Это делегирующий (проксированный) аккаунт + %s Вставьте json строку или загрузите файл… Загрузите файл JSON для восстановления diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 431649cf71..ab89650c70 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ - Delegated account %s doesn’t have enough balance to pay the network fee of %s. Available balance to pay fee: %s + Delegated account %s doesn\'t have enough balance to pay the network fee of %s. Available balance to pay fee: %s Not enough tokens to pay the fee This account granted access to perform transactions to the following account: @@ -14,7 +14,7 @@ Oops! Not enough permission %1$s delegated %2$s only for %3$s - Proxied wallets does not support signing arbitrary messages — only transactions + Proxied wallets do not support signing arbitrary messages — only transactions Delegated accounts update Nova Wallet automatically adds delegated authorities (Proxy) to a separate category for you. You can always manage wallets in Settings. From d9f50c4ca0d9c92562f08b3163199f35e9c19080 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:18:20 +0700 Subject: [PATCH 090/100] Fix - robonomics demoracy unlock call encoding (#1317) --- .../nova/common/utils/FearlessLibExt.kt | 3 +++ .../extrinsic/ExtrinsicBuilderExt.kt | 8 +++++++- .../nova/runtime/util/AccountLookup.kt | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 runtime/src/main/java/io/novafoundation/nova/runtime/util/AccountLookup.kt diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index da28c696d1..473a6a372f 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -77,6 +77,9 @@ fun String.isValidSS58Address() = runCatching { toAccountId() }.isSuccess fun String.removeHexPrefix() = removePrefix("0x") fun MetadataFunction.argument(name: String): FunctionArgument = arguments.first { it.name == name } + +fun MetadataFunction.argumentType(name: String): RuntimeType<*, *> = requireNotNull(argument(name).type) + fun FunctionArgument.requireActualType() = type?.skipAliases()!! fun Short.toByteArray(byteOrder: ByteOrder = ByteOrder.BIG_ENDIAN): ByteArray { 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 993de8bc59..b53f3effef 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 @@ -1,6 +1,8 @@ package io.novafoundation.nova.feature_governance_impl.data.network.blockchain.extrinsic import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.common.utils.argumentType +import io.novafoundation.nova.common.utils.democracy import io.novafoundation.nova.common.utils.structOf import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.AccountVote import io.novafoundation.nova.feature_governance_api.data.network.blockhain.model.ReferendumId @@ -8,10 +10,12 @@ import io.novafoundation.nova.feature_governance_api.data.network.blockhain.mode import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.runtime.extrinsic.multi.CallBuilder import io.novafoundation.nova.runtime.multiNetwork.runtime.types.custom.vote.Conviction +import io.novafoundation.nova.runtime.util.constructAccountLookupInstance import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.DictEnum import jp.co.soramitsu.fearless_utils.runtime.definitions.types.instances.AddressInstanceConstructor import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder +import jp.co.soramitsu.fearless_utils.runtime.metadata.call fun ExtrinsicBuilder.convictionVotingVote( referendumId: ReferendumId, @@ -70,11 +74,13 @@ fun ExtrinsicBuilder.democracyVote( } fun ExtrinsicBuilder.democracyUnlock(accountId: AccountId): ExtrinsicBuilder { + val accountLookupType = runtime.metadata.democracy().call("unlock").argumentType("target") + return call( moduleName = Modules.DEMOCRACY, callName = "unlock", arguments = mapOf( - "target" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, accountId) + "target" to accountLookupType.constructAccountLookupInstance(accountId) ) ) } diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/util/AccountLookup.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/util/AccountLookup.kt new file mode 100644 index 0000000000..60f41d62a9 --- /dev/null +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/util/AccountLookup.kt @@ -0,0 +1,20 @@ +package io.novafoundation.nova.runtime.util + +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.RuntimeType +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.composite.DictEnum +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.MULTI_ADDRESS_ID +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.primitives.FixedByteArray +import jp.co.soramitsu.fearless_utils.runtime.definitions.types.skipAliases + +fun RuntimeType<*, *>.constructAccountLookupInstance(accountId: AccountId): Any { + return when (skipAliases()) { + is DictEnum -> { // MultiAddress + DictEnum.Entry(MULTI_ADDRESS_ID, accountId) + } + is FixedByteArray -> { // GenericAccountId or similar + accountId + } + else -> throw UnsupportedOperationException("Unknown address type: ${this.name}") + } +} From ec2dae2c585204b410b4741658fbb93cca2dc260 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:17:18 +0700 Subject: [PATCH 091/100] Revisit usages of all accounts (#1318) --- .../handlers/ImportMnemonicDeepLinkHandler.kt | 6 +++--- .../nova/core_db/dao/MetaAccountDao.kt | 8 ++------ .../domain/interfaces/AccountInteractor.kt | 2 +- .../domain/interfaces/AccountRepository.kt | 7 +------ .../data/proxy/RealProxySyncService.kt | 2 +- .../data/repository/AccountRepositoryImpl.kt | 13 ++----------- .../data/repository/datasource/AccountDataSource.kt | 4 +--- .../repository/datasource/AccountDataSourceImpl.kt | 8 ++------ .../domain/AccountInteractorImpl.kt | 8 ++++---- .../domain/MetaAccountGroupingInteractorImpl.kt | 11 ++++++----- .../domain/StakingInteractor.kt | 2 +- .../session/RealWalletConnectSessionInteractor.kt | 2 +- 12 files changed, 25 insertions(+), 48 deletions(-) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ImportMnemonicDeepLinkHandler.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ImportMnemonicDeepLinkHandler.kt index 65134ab986..997320d502 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ImportMnemonicDeepLinkHandler.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/deepLinks/handlers/ImportMnemonicDeepLinkHandler.kt @@ -10,12 +10,12 @@ import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.awaitInteractionAllowed import io.novafoundation.nova.core.model.CryptoType import io.novafoundation.nova.feature_account_api.data.derivationPath.DerivationPathDecoder +import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.presenatation.account.add.AddAccountPayload import io.novafoundation.nova.feature_account_api.presenatation.account.add.ImportAccountPayload import io.novafoundation.nova.feature_account_api.presenatation.account.add.ImportType import io.novafoundation.nova.feature_account_api.presenatation.account.common.model.AdvancedEncryptionModel -import io.novafoundation.nova.feature_account_api.domain.account.common.EncryptionDefaults -import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.Mnemonic import jp.co.soramitsu.fearless_utils.encrypt.mnemonic.MnemonicCreator @@ -40,7 +40,7 @@ class ImportMnemonicDeepLinkHandler( } override suspend fun handleDeepLink(data: Uri) { - if (accountRepository.hasMetaAccounts()) { + if (accountRepository.hasActiveMetaAccounts()) { automaticInteractionGate.awaitInteractionAllowed() } diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index a28079bb72..e9d613fea4 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -104,10 +104,6 @@ interface MetaAccountDao { @Query("SELECT * FROM meta_accounts") suspend fun getMetaAccounts(): List - @Query("SELECT * FROM meta_accounts") - @Transaction - suspend fun getJoinedMetaAccountsInfo(): List - @Query("SELECT * FROM meta_accounts WHERE status = :status") @Transaction suspend fun getMetaAccountsInfoByStatus(status: MetaAccountLocal.Status): List @@ -185,8 +181,8 @@ interface MetaAccountDao { return metaId } - @Query("SELECT EXISTS(SELECT * FROM meta_accounts)") - suspend fun hasMetaAccounts(): Boolean + @Query("SELECT EXISTS(SELECT * FROM meta_accounts WHERE status = :status)") + suspend fun hasMetaAccountsByStatus(status: MetaAccountLocal.Status): Boolean @Query("UPDATE meta_accounts SET status = :status WHERE id IN (:metaIds)") suspend fun changeAccountsStatus(metaIds: List, status: MetaAccountLocal.Status) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt index 106aece206..a0aa602edd 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountInteractor.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.Flow interface AccountInteractor { - suspend fun getMetaAccounts(): List + suspend fun getActiveMetaAccounts(): List suspend fun generateMnemonic(): Mnemonic diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt index c3840b876e..3975ac8250 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/interfaces/AccountRepository.kt @@ -4,7 +4,6 @@ import io.novafoundation.nova.core.model.CryptoType import io.novafoundation.nova.core.model.Language import io.novafoundation.nova.core.model.Node import io.novafoundation.nova.feature_account_api.domain.model.Account -import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering @@ -42,11 +41,7 @@ interface AccountRepository { suspend fun accountNameFor(accountId: AccountId, chainId: ChainId): String? - suspend fun allMetaAccounts(): List - - suspend fun hasMetaAccounts(): Boolean - - suspend fun allLightMetaAccounts(): List + suspend fun hasActiveMetaAccounts(): Boolean fun allMetaAccountsFlow(): Flow> diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index 5f4da12278..a960fb06de 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -108,7 +108,7 @@ class RealProxySyncService( } private suspend fun getMetaAccounts(): List { - return accountRepository.allMetaAccounts() + return accountRepository.getActiveMetaAccounts() .filter { it.isAllowedToSyncProxy() } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt index cb8749d1ef..cef2adf9ca 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/AccountRepositoryImpl.kt @@ -18,7 +18,6 @@ import io.novafoundation.nova.feature_account_api.data.secrets.keypair import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.model.Account import io.novafoundation.nova.feature_account_api.domain.model.AuthType -import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountAssetBalance import io.novafoundation.nova.feature_account_api.domain.model.MetaAccountOrdering @@ -122,16 +121,8 @@ class AccountRepositoryImpl( return accountDataSource.accountNameFor(accountId, chainId) } - override suspend fun allMetaAccounts(): List { - return accountDataSource.allMetaAccounts() - } - - override suspend fun hasMetaAccounts(): Boolean { - return accountDataSource.hasMetaAccounts() - } - - override suspend fun allLightMetaAccounts(): List { - return accountDataSource.allLightMetaAccounts() + override suspend fun hasActiveMetaAccounts(): Boolean { + return accountDataSource.hasActiveMetaAccounts() } override fun allMetaAccountsFlow(): Flow> { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index e29557c232..2cd361e60d 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt @@ -45,8 +45,6 @@ interface AccountDataSource : SecretStoreV1 { suspend fun accountNameFor(accountId: AccountId, chainId: ChainId): String? - suspend fun allMetaAccounts(): List - suspend fun allLightMetaAccounts(): List fun allMetaAccountsFlow(): Flow> @@ -94,7 +92,7 @@ interface AccountDataSource : SecretStoreV1 { secrets: EncodableStruct ) - suspend fun hasMetaAccounts(): Boolean + suspend fun hasActiveMetaAccounts(): Boolean fun removeDeactivatedMetaAccounts() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt index 96056ebe54..36030a9cd4 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSourceImpl.kt @@ -142,10 +142,6 @@ class AccountDataSourceImpl( return metaAccountDao.metaAccountNameFor(accountId, chainId) } - override suspend fun allMetaAccounts(): List { - return metaAccountDao.getJoinedMetaAccountsInfo().map(::mapMetaAccountLocalToMetaAccount) - } - override suspend fun getActiveMetaAccounts(): List { return metaAccountDao.getMetaAccountsInfoByStatus(MetaAccountLocal.Status.ACTIVE) .map(::mapMetaAccountLocalToMetaAccount) @@ -273,8 +269,8 @@ class AccountDataSourceImpl( secretStoreV2.putChainAccountSecrets(metaId, accountId, secrets) } - override suspend fun hasMetaAccounts(): Boolean { - return metaAccountDao.hasMetaAccounts() + override suspend fun hasActiveMetaAccounts(): Boolean { + return metaAccountDao.hasMetaAccountsByStatus(MetaAccountLocal.Status.ACTIVE) } override fun removeDeactivatedMetaAccounts() { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt index 2c97495c97..b719e586a1 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/AccountInteractorImpl.kt @@ -25,8 +25,8 @@ class AccountInteractorImpl( private val accountRepository: AccountRepository, ) : AccountInteractor { - override suspend fun getMetaAccounts(): List { - return accountRepository.allMetaAccounts() + override suspend fun getActiveMetaAccounts(): List { + return accountRepository.getActiveMetaAccounts() } override suspend fun generateMnemonic(): Mnemonic { @@ -73,7 +73,7 @@ class AccountInteractorImpl( override suspend fun deleteAccount(metaId: Long) = withContext(Dispatchers.Default) { accountRepository.deleteAccount(metaId) if (!accountRepository.isAccountSelected()) { - val metaAccounts = getMetaAccounts() + val metaAccounts = getActiveMetaAccounts() if (metaAccounts.isNotEmpty()) { accountRepository.selectMetaAccount(metaAccounts.first().id) } @@ -185,7 +185,7 @@ class AccountInteractorImpl( accountRepository.removeDeactivatedMetaAccounts() if (!accountRepository.isAccountSelected()) { - val metaAccounts = getMetaAccounts() + val metaAccounts = getActiveMetaAccounts() if (metaAccounts.isNotEmpty()) { accountRepository.selectMetaAccount(metaAccounts.first().id) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt index cc81ab232b..e9ed2268c5 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/domain/MetaAccountGroupingInteractorImpl.kt @@ -54,7 +54,7 @@ class MetaAccountGroupingInteractorImpl( override fun metaAccountWithTotalBalanceFlow(metaId: Long): Flow { return combine( currencyRepository.observeSelectCurrency(), - accountRepository.allMetaAccountsFlow(), + accountRepository.activeMetaAccountsFlow(), accountRepository.metaAccountFlow(metaId), accountRepository.metaAccountBalancesFlow(metaId), chainRegistry.chainsById @@ -76,9 +76,10 @@ class MetaAccountGroupingInteractorImpl( metaAccountsUpdatesRegistry.observeUpdates(), accountRepository.allMetaAccountsFlow(), chainRegistry.chainsById - ) { updatedMetaIds, metaAccount, chainsById -> - val metaById = metaAccount.associateBy(MetaAccount::id) - metaAccount + ) { updatedMetaIds, metaAccounts, chainsById -> + val metaById = metaAccounts.associateBy(MetaAccount::id) + + metaAccounts .filter { it.type == LightMetaAccount.Type.PROXIED && updatedMetaIds.contains(it.id) } .mapNotNull { ProxiedAndProxyMetaAccount( @@ -128,7 +129,7 @@ class MetaAccountGroupingInteractorImpl( private suspend fun getValidMetaAccountsForTransaction(from: Chain, destination: Chain): List { val selectedMetaAccount = accountRepository.getSelectedMetaAccount() val fromChainAddress = selectedMetaAccount.addressIn(from) - return accountRepository.allMetaAccounts() + return accountRepository.getActiveMetaAccounts() .removed { fromChainAddress == it.addressIn(destination) } .filter { when (it.type) { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt index 9f2036aec7..dbe2257ca6 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt @@ -228,7 +228,7 @@ class StakingInteractor( suspend fun getAccountProjectionsInSelectedChains() = withContext(Dispatchers.Default) { val chain = stakingSharedState.chain() - accountRepository.allMetaAccounts().mapNotNull { + accountRepository.getActiveMetaAccounts().mapNotNull { mapAccountToStakingAccount(chain, it) } } diff --git a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionInteractor.kt b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionInteractor.kt index 05433fe91d..40d3ece981 100644 --- a/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionInteractor.kt +++ b/feature-wallet-connect-impl/src/main/java/io/novafoundation/nova/feature_wallet_connect_impl/domain/session/RealWalletConnectSessionInteractor.kt @@ -147,7 +147,7 @@ class RealWalletConnectSessionInteractor( walletConnectPairingRepository.allPairingAccountsFlow() ) { activeSessions, pairingAccounts -> val metaAccounts = if (metaId == null) { - accountRepository.allMetaAccounts() + accountRepository.getActiveMetaAccounts() } else { listOf(accountRepository.getMetaAccount(metaId)) } From c17d8ed840c1412b7a3869cdfe2b7dc0d0708076 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Fri, 12 Jan 2024 14:23:32 +0700 Subject: [PATCH 092/100] Fixes (#1324) * Fix - first proxy sync after update does not detect proxy chains in config * Fix - not enough permissions with no proxies case --- .../nova/app/root/domain/RootInteractor.kt | 7 +++++-- .../nova/app/root/presentation/RootViewModel.kt | 3 +++ common/src/main/res/values-ru/strings.xml | 1 + common/src/main/res/values/strings.xml | 1 + .../data/proxy/ProxySyncService.kt | 6 ++++++ .../data/proxy/RealProxySyncService.kt | 17 +++++++++++++++++ .../data/repository/RealProxyRepository.kt | 4 +++- .../proxy/sign/RealProxySigningPresenter.kt | 16 +++++++++++----- 8 files changed, 47 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt b/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt index d5a3fa40a8..ebd4d50039 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/domain/RootInteractor.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.feature_assets.data.network.BalancesUpdateSystem import io.novafoundation.nova.feature_buy_impl.domain.providers.ExternalProvider import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.mapLatest class RootInteractor( private val updateSystem: BalancesUpdateSystem, @@ -33,7 +34,9 @@ class RootInteractor( return accountRepository.isCodeSet() } - fun syncProxies() { - proxySyncService.startSyncing() + fun syncProxies(): Flow<*> { + return proxySyncService.proxySyncTrigger().mapLatest { + proxySyncService.startSyncingSuspend() + } } } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt index eaf791122c..ba2c21d6a6 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/presentation/RootViewModel.kt @@ -14,6 +14,7 @@ import io.novafoundation.nova.common.mixin.api.NetworkStateUi import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.sequrity.SafeModeService import io.novafoundation.nova.common.utils.coroutines.RootScope +import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate import io.novafoundation.nova.common.utils.sequrity.BackgroundAccessObserver import io.novafoundation.nova.core.updater.Updater @@ -119,6 +120,8 @@ class RootViewModel( private fun syncProxies() { interactor.syncProxies() + .inBackground() + .launchIn(rootScope) } private fun handleUpdatesSideEffect(sideEffect: Updater.SideEffect) { diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index ee08eee13b..f5e8f82488 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -672,6 +672,7 @@ Что такое прокси? Делегированный аккаунт %s не имеет достаточного баланса для оплаты сетевой комиссии %s. Доступный баланс для оплаты комиссии: %s Проксированные кошельки не поддерживают подпись произвольных сообщений - только транзакций + %1$s не делегировал прав %2$s %1$s делегировал %2$s только для %3$s Упс! Недостаточно разрешений Транзакция будет инициирована %s как делегированным аккаунтом. Сетевая комиссия будет оплачена делегированным аккаунтом. diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index ab89650c70..dea0797445 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,5 +1,6 @@ + %1$s has not delegated %2$s Delegated account %s doesn\'t have enough balance to pay the network fee of %s. Available balance to pay fee: %s Not enough tokens to pay the fee diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt index b54663ec26..aff144b10d 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/proxy/ProxySyncService.kt @@ -1,6 +1,12 @@ package io.novafoundation.nova.feature_account_api.data.proxy +import kotlinx.coroutines.flow.Flow + interface ProxySyncService { + fun proxySyncTrigger(): Flow<*> + fun startSyncing() + + suspend fun startSyncingSuspend() } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt index a960fb06de..9be3511bbf 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/RealProxySyncService.kt @@ -26,6 +26,9 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.multiNetwork.findChains import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch class RealProxySyncService( @@ -40,18 +43,32 @@ class RealProxySyncService( private val shouldSyncWatchOnlyProxies: Boolean ) : ProxySyncService { + override fun proxySyncTrigger(): Flow<*> { + return chainRegistry.currentChains.map { chains -> + chains + .filter(Chain::supportProxy) + .map(Chain::id) + }.distinctUntilChanged() + } + override fun startSyncing() { rootScope.launch(Dispatchers.Default) { startSyncInternal() } } + override suspend fun startSyncingSuspend() { + startSyncInternal() + } + private suspend fun startSyncInternal() = runCatching { val metaAccounts = getMetaAccounts() if (metaAccounts.isEmpty()) return@runCatching val supportedProxyChains = getSupportedProxyChains() + Log.d(LOG_TAG, "Starting syncing proxies in ${supportedProxyChains.size} chains") + supportedProxyChains.forEach { chain -> syncChainProxies(chain, metaAccounts) } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt index e7d1886d74..9259474815 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/RealProxyRepository.kt @@ -15,10 +15,10 @@ import io.novafoundation.nova.feature_account_impl.data.mappers.mapProxyTypeToSt import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource -import java.math.BigInteger import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.metadata.module import jp.co.soramitsu.fearless_utils.runtime.metadata.storage +import java.math.BigInteger private class OnChainProxyModel( val accountId: AccountIdKey, @@ -82,6 +82,8 @@ class RealProxyRepository( } private fun bindProxyAccounts(dynamicInstance: Any?): List { + if (dynamicInstance == null) return emptyList() + val root = dynamicInstance.castToList() val proxies = root[0].castToList() diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt index ccd55fdb37..d7fbc7dcf8 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/proxy/sign/RealProxySigningPresenter.kt @@ -17,9 +17,9 @@ import io.novafoundation.nova.feature_account_impl.presentation.common.sign.notS import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatTokenAmount import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain -import java.math.BigInteger import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.math.BigInteger import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -123,15 +123,21 @@ class RealProxySigningPresenter( proxyMetaAccount: MetaAccount, proxyTypes: List ): CharSequence { - val subtitle = resourceManager.getString(R.string.proxy_signing_not_enough_permission_message) val primaryColor = resourceManager.getColor(R.color.text_primary) val proxiedName = proxiedMetaAccount.name.toSpannable(colorSpan(primaryColor)) val proxyName = proxyMetaAccount.name.toSpannable(colorSpan(primaryColor)) - val proxyTypesBuffer = SpannableStringBuilder() - val proxyTypesCharSequence = proxyTypes.joinTo(proxyTypesBuffer) { it.name.toSpannable(colorSpan(primaryColor)) } + return if (proxyTypes.isNotEmpty()) { + val subtitle = resourceManager.getString(R.string.proxy_signing_not_enough_permission_message) - return SpannableFormatter.format(subtitle, proxiedName, proxyName, proxyTypesCharSequence) + val proxyTypesBuffer = SpannableStringBuilder() + val proxyTypesCharSequence = proxyTypes.joinTo(proxyTypesBuffer) { it.name.toSpannable(colorSpan(primaryColor)) } + + SpannableFormatter.format(subtitle, proxiedName, proxyName, proxyTypesCharSequence) + } else { + val subtitle = resourceManager.getString(R.string.proxy_signing_none_permissions_message) + SpannableFormatter.format(subtitle, proxiedName, proxyName) + } } } From 27f98c2512d12118c3343952c9c640e07f19a765 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Fri, 12 Jan 2024 16:57:15 +0700 Subject: [PATCH 093/100] Non proxy fixes (#1325) * Update github update link * Fix crash when failed to retrieve metadata * Fix tests --- app/build.gradle | 2 +- .../runtime/RuntimeSyncService.kt | 25 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6ca8ca4f40..2f888b3f8c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,7 +73,7 @@ android { versionNameSuffix '-github' applicationIdSuffix '.github' - buildConfigField "String", "APP_UPDATE_SOURCE_LINK", "\"https://github.com/novasamatech/nova-wallet-android-releases/releases\"" + buildConfigField "String", "APP_UPDATE_SOURCE_LINK", "\"https://github.com/novasamatech/nova-wallet-android/releases\"" } develop { signingConfig signingConfigs.dev diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeSyncService.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeSyncService.kt index dc0c2b3a04..17a0b438d2 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeSyncService.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/multiNetwork/runtime/RuntimeSyncService.kt @@ -97,22 +97,27 @@ class RuntimeSyncService( cancelExistingSync(chainId) syncingChains[chainId] = launch(syncDispatcher) { - sync(chainId, forceFullSync) + runCatching { + val syncResult = sync(chainId, forceFullSync) + syncResult?.let { _syncStatusFlow.emit(it) } + } + + syncFinished(chainId) } } private suspend fun sync( chainId: String, forceFullSync: Boolean, - ) { + ): SyncResult? { val syncInfo = knownChains[chainId] if (syncInfo == null) { Log.w(LOG_TAG, "Unknown chain with id $chainId requested to be synced") - return + return null } - val runtimeInfo = chainDao.runtimeInfo(chainId) ?: return + val runtimeInfo = chainDao.runtimeInfo(chainId) ?: return null val shouldSyncMetadata = runtimeInfo.shouldSyncMetadata() || forceFullSync @@ -138,14 +143,10 @@ class RuntimeSyncService( } } - syncFinished(chainId) - - _syncStatusFlow.emit( - SyncResult( - metadataHash = metadataHash, - typesHash = typesHash, - chainId = chainId - ) + return SyncResult( + metadataHash = metadataHash, + typesHash = typesHash, + chainId = chainId ) } From ac9451dec86725c29fb2416c155fe3874f4dfc9f Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:55:17 +0300 Subject: [PATCH 094/100] Fix/staking reactive updates for relaychain staking (#1320) * Fix reactive updates for relaychain unbondings * Fix reactive updates for relaychain stake summary --- .../domain/api/StakingRepository.kt | 2 +- .../data/repository/StakingRepositoryImpl.kt | 4 +-- .../domain/StakingInteractor.kt | 11 +++---- .../domain/staking/unbond/UnbondInteractor.kt | 32 ++++++++----------- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/api/StakingRepository.kt b/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/api/StakingRepository.kt index 352058061b..bdc4e631e9 100644 --- a/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/api/StakingRepository.kt +++ b/feature-staking-api/src/main/java/io/novafoundation/nova/feature_staking_api/domain/api/StakingRepository.kt @@ -47,7 +47,7 @@ interface StakingRepository { fun stakingStoriesFlow(): Flow> - suspend fun ledgerFlow(stakingState: StakingState.Stash): Flow + fun ledgerFlow(stakingState: StakingState.Stash): Flow suspend fun ledger(chainId: ChainId, accountId: AccountId): StakingLedger? diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt index e3f3be6737..1d1409e22c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt @@ -280,8 +280,8 @@ class StakingRepositoryImpl( return stakingStoriesDataSource.getStoriesFlow() } - override suspend fun ledgerFlow(stakingState: StakingState.Stash): Flow { - return localStorage.query(stakingState.chain.id) { + override fun ledgerFlow(stakingState: StakingState.Stash): Flow { + return localStorage.subscribe(stakingState.chain.id) { metadata.staking.ledger.observe(stakingState.controllerId) }.filterNotNull() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt index dbe2257ca6..165a8de2c1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/StakingInteractor.kt @@ -1,5 +1,6 @@ package io.novafoundation.nova.feature_staking_impl.domain +import io.novafoundation.nova.common.utils.combineToPair import io.novafoundation.nova.common.utils.flowOfAll import io.novafoundation.nova.common.utils.isZero import io.novafoundation.nova.common.utils.sumByBigInteger @@ -49,9 +50,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.FlowCollector -import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext @@ -293,10 +294,10 @@ class StakingInteractor( val chainAsset = stakingSharedState.chainAsset() val chainId = chainAsset.chainId - combineTransform( + combineToPair( stakingSharedComputation.activeEraInfo(chainId, scope), walletRepository.assetFlow(state.accountId, chainAsset) - ) { activeEraInfo, asset -> + ).flatMapLatest { (activeEraInfo, asset) -> val activeStake = asset.bondedInPlanks val rewardedNominatorsPerValidator = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId) @@ -308,14 +309,12 @@ class StakingInteractor( activeStake = activeStake ) - val summary = flow { statusResolver(statusResolutionContext) }.map { status -> + flow { statusResolver(statusResolutionContext) }.map { status -> StakeSummary( status = status, activeStake = activeStake ) } - - emitAll(summary) } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/unbond/UnbondInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/unbond/UnbondInteractor.kt index e71a0f622d..36682208a1 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/unbond/UnbondInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/unbond/UnbondInteractor.kt @@ -1,6 +1,6 @@ package io.novafoundation.nova.feature_staking_impl.domain.staking.unbond -import io.novafoundation.nova.common.utils.flowOfAll +import io.novafoundation.nova.common.utils.combineToPair import io.novafoundation.nova.common.utils.sumByBigInteger import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicSubmission @@ -18,8 +18,7 @@ import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combineTransform -import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext import java.math.BigInteger @@ -56,22 +55,17 @@ class UnbondInteractor( } fun unbondingsFlow(stakingState: StakingState.Stash, sharedComputationScope: CoroutineScope): Flow { - return flowOfAll { - combineTransform( - stakingRepository.ledgerFlow(stakingState), - stakingRepository.observeActiveEraIndex(stakingState.chain.id) - ) { ledger, activeEraIndex -> - - val unbondingsFlow = stakingSharedComputation.constructUnbondingList( - eraRedeemables = ledger.unlocking, - activeEra = activeEraIndex, - stakingOption = stakingSharedState.selectedOption(), - sharedComputationScope = sharedComputationScope - ).map { unbondings -> - Unbondings.from(unbondings, rebondPossible = true) - } - - emitAll(unbondingsFlow) + return combineToPair( + stakingRepository.ledgerFlow(stakingState), + stakingRepository.observeActiveEraIndex(stakingState.chain.id) + ).flatMapLatest { (ledger, activeEraIndex) -> + stakingSharedComputation.constructUnbondingList( + eraRedeemables = ledger.unlocking, + activeEra = activeEraIndex, + stakingOption = stakingSharedState.selectedOption(), + sharedComputationScope = sharedComputationScope + ).map { unbondings -> + Unbondings.from(unbondings, rebondPossible = true) } } } From 4bf8bf4f9714bf658460625d6d26753f2f74d5c1 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:55:56 +0300 Subject: [PATCH 095/100] Fix/tests (#1319) * Fix integration tests * Increase timeout back --- .../nova/balances/BalancesIntegrationTest.kt | 23 ++++++++++++++++++- .../nova/common/utils/FearlessLibExt.kt | 3 +++ .../nova/runtime/ext/ChainExt.kt | 3 ++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt b/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt index 7b1bebb9ab..b26e843128 100644 --- a/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt +++ b/app/src/androidTest/java/io/novafoundation/nova/balances/BalancesIntegrationTest.kt @@ -6,11 +6,15 @@ import com.google.gson.Gson import io.novafoundation.nova.common.data.network.runtime.binding.AccountInfo import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountInfo import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.utils.emptyEthereumAccountId +import io.novafoundation.nova.common.utils.emptySubstrateAccountId import io.novafoundation.nova.common.utils.fromJson import io.novafoundation.nova.common.utils.hasModule import io.novafoundation.nova.common.utils.system import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent import io.novafoundation.nova.runtime.BuildConfig.TEST_CHAINS_URL import io.novafoundation.nova.runtime.di.RuntimeApi @@ -123,7 +127,7 @@ class BalancesIntegrationTest( private suspend fun testFeeLoadingAsync(chain: Chain) { return coroutineScope { withTimeout(80.seconds) { - extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { + extrinsicService.estimateFee(chain, testTransactionOrigin()) { systemRemark(byteArrayOf(0)) val haveBatch = runtime.metadata.hasModule("Utility") @@ -134,4 +138,21 @@ class BalancesIntegrationTest( } } } + + private fun testTransactionOrigin(): TransactionOrigin = TransactionOrigin.Wallet( + MetaAccount( + id = 0, + chainAccounts = emptyMap(), + proxy = null, + substratePublicKey = null, + substrateCryptoType = null, + substrateAccountId = emptySubstrateAccountId(), + ethereumAddress = emptyEthereumAccountId(), + ethereumPublicKey = null, + isSelected = true, + name = "Test", + type = LightMetaAccount.Type.WATCH_ONLY, + status = LightMetaAccount.Status.ACTIVE + ) + ) } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt index 473a6a372f..fdf5dca621 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/FearlessLibExt.kt @@ -305,6 +305,9 @@ fun String.ethereumAddressToAccountId() = asEthereumAddress().toAccountId().valu fun AccountId.ethereumAccountIdToAddress(withChecksum: Boolean = true) = asEthereumAccountId().toAddress(withChecksum).value fun emptyEthereumAccountId() = ByteArray(20) { 1 } + +fun emptySubstrateAccountId() = ByteArray(32) + fun emptyEthereumAddress() = emptyEthereumAccountId().ethereumAccountIdToAddress(withChecksum = false) val SignerPayloadExtrinsic.chainId: String diff --git a/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt b/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt index fb129a8df1..685e5ee2d3 100644 --- a/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt +++ b/runtime/src/main/java/io/novafoundation/nova/runtime/ext/ChainExt.kt @@ -4,6 +4,7 @@ import io.novafoundation.nova.common.data.network.runtime.binding.MultiAddress import io.novafoundation.nova.common.data.network.runtime.binding.bindOrNull import io.novafoundation.nova.common.utils.Modules import io.novafoundation.nova.common.utils.emptyEthereumAccountId +import io.novafoundation.nova.common.utils.emptySubstrateAccountId import io.novafoundation.nova.common.utils.findIsInstanceOrNull import io.novafoundation.nova.common.utils.formatNamed import io.novafoundation.nova.common.utils.substrateAccountId @@ -178,7 +179,7 @@ fun Chain.accountIdOrNull(address: String): ByteArray? { fun Chain.emptyAccountId() = if (isEthereumBased) { emptyEthereumAccountId() } else { - ByteArray(32) + emptySubstrateAccountId() } fun Chain.accountIdOrDefault(maybeAddress: String): ByteArray { From 7cbde4d29370361a27d732570a89b29141f891a6 Mon Sep 17 00:00:00 2001 From: valentunn <70131744+valentunn@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:56:11 +0300 Subject: [PATCH 096/100] Use wrapped payload for fee validation (#1327) --- .../data/signer/proxy/ProxiedSigner.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 12f08682ef..745d2ba107 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 @@ -4,6 +4,8 @@ import io.novafoundation.nova.common.base.errors.SigningCancelledException import io.novafoundation.nova.common.utils.chainId import io.novafoundation.nova.common.utils.toCallInstance import io.novafoundation.nova.common.validation.ValidationStatus +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee +import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationPayload import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxyExtrinsicValidationRequestBus import io.novafoundation.nova.feature_account_api.data.repository.ProxyRepository import io.novafoundation.nova.feature_account_api.data.signer.SignerProvider @@ -13,8 +15,6 @@ import io.novafoundation.nova.feature_account_api.domain.model.ProxyAccount.Prox import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn import io.novafoundation.nova.feature_account_api.domain.model.requireAddressIn import io.novafoundation.nova.feature_account_api.presenatation.account.proxy.ProxySigningPresenter -import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationFailure.ProxyNotEnoughFee -import io.novafoundation.nova.feature_account_api.data.proxy.validation.ProxiedExtrinsicValidationPayload import io.novafoundation.nova.runtime.ext.commissionAsset import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -79,12 +79,18 @@ class ProxiedSigner( acknowledgeProxyOperation(proxyMetaAccount) + // TODO this wont use the actual payload for fee validation when multiple nested proxies are used + // We need to design a universal solution + // We actually can use `signedExtrinsic.payload` to access actual payload but in this case validation will happen only after signing + // which will have bad UX with Vault and Ledger. + // As an option we could separate signing and wrapping step specifically for such nested signers and use only the wrapping step before fee validation + val modifiedPayload = modifyPayload(proxyMetaAccount, payloadExtrinsic, chain) + if (isRootProxied) { - validateExtrinsic(payloadExtrinsic, chain) + validateExtrinsic(modifiedPayload, chain) } val delegate = createDelegate(proxyMetaAccount) - val modifiedPayload = modifyPayload(proxyMetaAccount, payloadExtrinsic, chain) val signedExtrinsic = delegate.signExtrinsic(modifiedPayload) return signedExtrinsic From 8d3ec2d85e04344d5307901eeea3c52df661c77e Mon Sep 17 00:00:00 2001 From: antonijzelinskij <107959809+antonijzelinskij@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:40:52 +0500 Subject: [PATCH 097/100] Fixed res names and values: stacking to staking and unstacking to unstaking (#1328) --- common/src/main/res/values-ru/strings.xml | 4 ++-- common/src/main/res/values/strings.xml | 14 ++++++++++---- .../change/ChangeStakingValidationFailureUI.kt | 4 ++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index f5e8f82488..ad8dfc566e 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -852,8 +852,8 @@ Неподдерживаемый тип стейкинга sr25519 (рекомендованный) Schnorrkel - Добавьте учетную запись контроллера в устройство. - Нет доступа к учетной записи контроллера + Добавьте учетную запись контроллера в устройство. + Нет доступа к учетной записи контроллера Выбранный аккаунт уже используется в качестве контроллера. Активные делегаторы Чтобы выполнить это действие, добавьте контроллер аккаунт %s в приложение diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index dea0797445..2ee9e96328 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,5 +1,15 @@ + + + No access to controller account + + + Add your controller account in device. + + + Staking is an option to earn passive income by locking your tokens in the network. Staking rewards are allocated every era (6 hours on Kusama and 24 hours on Polkadot). You can stake as long as you wish, and for unstaking your tokens you need to wait for the unstaking period to end, making your tokens available to be redeemed. + %1$s has not delegated %2$s Delegated account %s doesn\'t have enough balance to pay the network fee of %s. Available balance to pay fee: %s @@ -625,9 +635,6 @@ Safe mode - No access to controller account - Add your controller account in device. - Page settings Add to Favorites Remove from Favorites @@ -1633,7 +1640,6 @@ Rewards for staking are available to payout at the end of each era (6 hours in Kusama and 24 hours in Polkadot). Network stores pending rewards during 84 eras and in most cases validators are paying out the rewards for everyone. However, validators might forget or something might happen with them, so nominators can payout their rewards by themselves. Although rewards are usually distributed by validators, Nova Wallet helps by alerting if there are any unpaid rewards that are close to expiring. You will receive alerts about this and other activities on the staking screen. Receiving rewards - Staking is an option to earn passive income by locking your tokens in the network. Staking rewards are allocated every era (6 hours on Kusama and 24 hours on Polkadot). You can stake as long as you wish, and for unstaking your tokens you need to wait for the unstacking period to end, making your tokens available to be redeemed. Staking is an important part of network security and reliability. Anyone can run validator nodes, but only those who have enough tokens staked will be elected by the network to participate in composing new blocks and receive the rewards. Validators often do not have enough tokens by themselves, so nominators are helping them by locking their tokens for them to achieve the required amount of stake. What is staking? The validator runs a blockchain node 24/7 and is required to have enough stake locked (both owned and provided by nominators) to be elected by the network. Validators should maintain their nodes\' performance and reliability to be rewarded. Being a validator is almost a full-time job, there are companies that are focused to be validators on the blockchain networks. diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/ChangeStakingValidationFailureUI.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/ChangeStakingValidationFailureUI.kt index d7bca03440..55afbd8826 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/ChangeStakingValidationFailureUI.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/validators/change/ChangeStakingValidationFailureUI.kt @@ -11,8 +11,8 @@ fun mapAddEvmTokensValidationFailureToUI( ): TitleAndMessage { return when (failure) { ChangeStackingValidationFailure.NO_ACCESS_TO_CONTROLLER_ACCOUNT -> { - resourceManager.getString(R.string.stacking_no_access_to_controller_account_title) to - resourceManager.getString(R.string.stacking_no_access_to_controller_account_message) + resourceManager.getString(R.string.staking_no_access_to_controller_account_title) to + resourceManager.getString(R.string.staking_no_access_to_controller_account_message) } } } From e63857a176fbd4c58260d52c812025cd7277bd3f Mon Sep 17 00:00:00 2001 From: Valentun Date: Tue, 16 Jan 2024 12:46:40 +0300 Subject: [PATCH 098/100] Apply localization changes to localize --- common/src/main/res/values/strings.xml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 2ee9e96328..668c980cc4 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,13 +1,9 @@ - No access to controller account - - Add your controller account in device. - Staking is an option to earn passive income by locking your tokens in the network. Staking rewards are allocated every era (6 hours on Kusama and 24 hours on Polkadot). You can stake as long as you wish, and for unstaking your tokens you need to wait for the unstaking period to end, making your tokens available to be redeemed. %1$s has not delegated %2$s From 30ff59cd007f8c3dfc9fcc7204b3413d1450900e Mon Sep 17 00:00:00 2001 From: Valentun Date: Thu, 18 Jan 2024 00:54:05 +0300 Subject: [PATCH 099/100] Fix - cannot lookup meta account when there is a chain account in another chain --- .../java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt index e9d613fea4..74abb8e5b4 100644 --- a/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt +++ b/core-db/src/main/java/io/novafoundation/nova/core_db/dao/MetaAccountDao.kt @@ -26,9 +26,9 @@ import java.math.BigInteger */ @Language("RoomSql") private const val FIND_BY_ADDRESS_WHERE_CLAUSE = """ - LEFT JOIN chain_accounts as c ON m.id = c.metaId + LEFT JOIN chain_accounts as c ON m.id = c.metaId AND c.chainId = :chainId WHERE - (c.chainId = :chainId AND c.accountId IS NOT NULL AND c.accountId = :accountId) + (c.accountId IS NOT NULL AND c.accountId = :accountId) OR (c.accountId IS NULL AND (substrateAccountId = :accountId OR ethereumAddress = :accountId)) ORDER BY (CASE WHEN isSelected THEN 0 ELSE 1 END) """ From 217e1af793016eb564e0745180643bb90d66eb07 Mon Sep 17 00:00:00 2001 From: antonijzelinskij Date: Thu, 18 Jan 2024 08:08:51 +0100 Subject: [PATCH 100/100] Bump version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f3d841956d..d617c447f3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ buildscript { ext { // App version - versionName = '7.6.3' - versionCode = 107 + versionName = '7.7.1' + versionCode = 109 applicationId = "io.novafoundation.nova" releaseApplicationSuffix = "market"