diff --git a/app/build.gradle b/app/build.gradle index 2f888b3f8c..6d3025d2b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -210,6 +210,9 @@ dependencies { implementation project(':feature-wallet-connect-api') implementation project(':feature-wallet-connect-impl') + implementation project(':feature-proxy-api') + implementation project(':feature-proxy-impl') + implementation project(':feature-settings-api') implementation project(':feature-settings-impl') diff --git a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/AccountNavigationModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/AccountNavigationModule.kt index 3440ff71f3..85c98c2b3e 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/AccountNavigationModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/app/navigation/AccountNavigationModule.kt @@ -10,7 +10,7 @@ import io.novafoundation.nova.app.root.navigation.account.SelectWalletCommunicat import io.novafoundation.nova.app.root.navigation.pincode.PinCodeTwoFactorVerificationCommunicatorImpl import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationCommunicator -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter diff --git a/app/src/main/java/io/novafoundation/nova/app/di/deps/ComponentHolderModule.kt b/app/src/main/java/io/novafoundation/nova/app/di/deps/ComponentHolderModule.kt index 5d43bffc76..d491dac546 100644 --- a/app/src/main/java/io/novafoundation/nova/app/di/deps/ComponentHolderModule.kt +++ b/app/src/main/java/io/novafoundation/nova/app/di/deps/ComponentHolderModule.kt @@ -36,6 +36,8 @@ import io.novafoundation.nova.feature_nft_api.NftFeatureApi import io.novafoundation.nova.feature_nft_impl.di.NftFeatureHolder import io.novafoundation.nova.feature_onboarding_api.di.OnboardingFeatureApi import io.novafoundation.nova.feature_onboarding_impl.di.OnboardingFeatureHolder +import io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi +import io.novafoundation.nova.feature_proxy_impl.di.ProxyFeatureHolder import io.novafoundation.nova.feature_settings_api.SettingsFeatureApi import io.novafoundation.nova.feature_settings_impl.di.SettingsFeatureHolder import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi @@ -207,4 +209,10 @@ interface ComponentHolderModule { @ClassKey(BuyFeatureApi::class) @IntoMap fun provideBuyFeature(holder: BuyFeatureHolder): FeatureApiHolder + + @ApplicationScope + @Binds + @ClassKey(io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi::class) + @IntoMap + fun provideProxyFeature(holder: ProxyFeatureHolder): FeatureApiHolder } diff --git a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectAddressCommunicatorImpl.kt b/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectAddressCommunicatorImpl.kt index 9b150eca59..c74f095764 100644 --- a/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectAddressCommunicatorImpl.kt +++ b/app/src/main/java/io/novafoundation/nova/app/root/navigation/account/SelectAddressCommunicatorImpl.kt @@ -2,17 +2,17 @@ package io.novafoundation.nova.app.root.navigation.account import io.novafoundation.nova.app.root.navigation.NavStackInterScreenCommunicator import io.novafoundation.nova.app.root.navigation.NavigationHolder -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressCommunicator -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_api.presenatation.mixin.selectAddress.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressRequester +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressResponder import io.novafoundation.nova.feature_account_impl.presentation.account.list.selectAddress.SelectAddressFragment import io.novafoundation.nova.feature_assets.presentation.AssetsRouter class SelectAddressCommunicatorImpl(private val router: AssetsRouter, navigationHolder: NavigationHolder) : - NavStackInterScreenCommunicator(navigationHolder), + NavStackInterScreenCommunicator(navigationHolder), SelectAddressCommunicator { - override fun openRequest(request: SelectAddressForTransactionRequester.Request) { + override fun openRequest(request: SelectAddressRequester.Request) { super.openRequest(request) router.openSelectAddress(SelectAddressFragment.getBundle(request)) 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 cf99622a45..38be483e81 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 @@ -72,4 +72,8 @@ class ParachainStakingNavigator( actionId = R.id.action_setupYieldBoostFragment_to_yieldBoostConfirmFragment, args = YieldBoostConfirmFragment.getBundle(payload) ) + + override fun openAddStakingProxy() { + performNavigation(R.id.action_open_addStakingProxyFragment) + } } 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 2c797cef2e..1cf4a04603 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 @@ -19,8 +19,12 @@ import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.con import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm.ConfirmBondMorePayload import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.select.SelectBondMoreFragment import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.select.SelectBondMorePayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm.ConfirmSetControllerFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm.ConfirmSetControllerPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyPayload import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.model.StakingStoryModel import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.confirm.ConfirmRebondFragment import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.confirm.ConfirmRebondPayload @@ -222,4 +226,24 @@ class RelayStakingNavigator( returnToStakingMain() } } + + override fun openAddStakingProxy() { + performNavigation(R.id.action_open_addStakingProxyFragment) + } + + override fun openConfirmAddStakingProxy(payload: ConfirmAddStakingProxyPayload) { + performNavigation( + R.id.action_addStakingProxyFragment_to_confirmAddStakingProxyFragment, + ConfirmAddStakingProxyFragment.getBundle(payload) + ) + } + + override fun openStakingProxyList() { + performNavigation(R.id.action_open_stakingProxyList) + } + + override fun openConfirmRemoveStakingProxy(payload: ConfirmRemoveStakingProxyPayload) { + val arguments = ConfirmRemoveStakingProxyFragment.getBundle(payload) + performNavigation(R.id.action_open_confirmRemoveStakingProxyFragment, arguments) + } } diff --git a/app/src/main/res/navigation/staking_main_graph.xml b/app/src/main/res/navigation/staking_main_graph.xml index e30a403d20..e92cda80a4 100644 --- a/app/src/main/res/navigation/staking_main_graph.xml +++ b/app/src/main/res/navigation/staking_main_graph.xml @@ -29,6 +29,60 @@ app:popEnterAnim="@anim/fragment_close_enter" app:popExitAnim="@anim/fragment_close_exit" /> + + + + + + + + + + + + + + + + + + @@ -303,7 +357,7 @@ @@ -335,6 +389,7 @@ android:name="io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm.ConfirmBondMoreFragment" android:label="ConfirmBondMoreFragment" tools:layout="@layout/fragment_confirm_bond_more" /> + : Filter { val options: List } +class EverythingFilter : Filter { + + override fun shouldInclude(model: T) = true +} + fun List.applyFilters(filters: List>): List { return filter { item -> filters.all { filter -> filter.shouldInclude(item) } } } diff --git a/common/src/main/java/io/novafoundation/nova/common/utils/keyboard/KeyboardExt.kt b/common/src/main/java/io/novafoundation/nova/common/utils/keyboard/KeyboardExt.kt index d808719f51..362f125a31 100644 --- a/common/src/main/java/io/novafoundation/nova/common/utils/keyboard/KeyboardExt.kt +++ b/common/src/main/java/io/novafoundation/nova/common/utils/keyboard/KeyboardExt.kt @@ -27,6 +27,9 @@ fun View.showSoftKeyboard() { inputMethodManager.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) } +/** + * Make sure that the insets are not consumed by the layer above for this method to work correctly + */ fun Lifecycle.setKeyboardVisibilityListener(view: View, callback: KeyboardVisibilityCallback?) { if (callback == null) { ViewCompat.setOnApplyWindowInsetsListener(view, null) diff --git a/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt b/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt index 384456962c..4da2908487 100644 --- a/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt +++ b/common/src/main/java/io/novafoundation/nova/common/view/TableCellView.kt @@ -227,6 +227,9 @@ open class TableCellView @JvmOverloads constructor( val titleText = typedArray.getString(R.styleable.TableCellView_title) setTitle(titleText) + val primaryValueText = typedArray.getString(R.styleable.TableCellView_primaryValue) + primaryValueText?.let { showValue(it) } + val dividerVisible = typedArray.getBoolean(R.styleable.TableCellView_dividerVisible, true) setDividerVisible(dividerVisible) @@ -256,9 +259,9 @@ open class TableCellView @JvmOverloads constructor( val titleIconStart = typedArray.getResourceIdOrNull(R.styleable.TableCellView_titleIconStart) titleIconStart?.let { - val titleIconTint = typedArray.getResourceIdOrNull(R.styleable.TableCellView_titleIconTint) + val titleIconStartTint = typedArray.getResourceIdOrNull(R.styleable.TableCellView_titleIconStartTint) - setTitleIconStart(titleIconStart, titleIconTint) + setTitleIconStart(titleIconStart, titleIconStartTint) } val titleTextAppearance = typedArray.getResourceIdOrNull(R.styleable.TableCellView_titleValueTextAppearance) diff --git a/common/src/main/java/io/novafoundation/nova/common/view/YourWalletsView.kt b/common/src/main/java/io/novafoundation/nova/common/view/YourWalletsView.kt new file mode 100644 index 0000000000..5d77521a4f --- /dev/null +++ b/common/src/main/java/io/novafoundation/nova/common/view/YourWalletsView.kt @@ -0,0 +1,29 @@ +package io.novafoundation.nova.common.view + +import android.content.Context +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.widget.LinearLayout +import androidx.core.view.setPadding +import io.novafoundation.nova.common.R +import io.novafoundation.nova.common.utils.dp +import io.novafoundation.nova.common.utils.getRippleMask +import io.novafoundation.nova.common.utils.getRoundedCornerDrawable +import io.novafoundation.nova.common.utils.withRippleMask + +class YourWalletsView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + + init { + View.inflate(context, R.layout.view_your_wallets, this) + + setPadding(4.dp) + background = getRoundedCornerDrawable(cornerSizeDp = 8).withRippleMask(getRippleMask(cornerSizeDp = 8)) + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + } +} diff --git a/feature-account-impl/src/main/res/drawable/ic_delete.xml b/common/src/main/res/drawable/ic_clear_pin_code_outline.xml similarity index 100% rename from feature-account-impl/src/main/res/drawable/ic_delete.xml rename to common/src/main/res/drawable/ic_clear_pin_code_outline.xml diff --git a/common/src/main/res/drawable/ic_delete.xml b/common/src/main/res/drawable/ic_delete.xml new file mode 100644 index 0000000000..59a04a89f8 --- /dev/null +++ b/common/src/main/res/drawable/ic_delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/src/main/res/layout/view_your_wallets.xml b/common/src/main/res/layout/view_your_wallets.xml new file mode 100644 index 0000000000..9382efe241 --- /dev/null +++ b/common/src/main/res/layout/view_your_wallets.xml @@ -0,0 +1,38 @@ + + + + + + + + + \ No newline at end of file diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index ad8dfc566e..6a60331e1d 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -142,6 +142,13 @@ Изменить аккаунт %s Изменить аккаунт Вы должны добавить %s аккаунт в кошелек, чтобы иметь возможность делегировать + Делегировать аккаунту + Аккаунт делегирования + Кошелек делегирования + Тип доступа + Депозит остается зарезервированным на вашем счете до тех пор, пока прокси не будет удален. + Вы достигли лимита добавленных прокси (%s) в %s . Удалите прокси, чтобы добавить новые. + Достигнуто максимальное количество прокси Введенный адрес контракта уже добавлен в Nova как токен %s. Введенный адрес контракта присутствует в Nova как токен %s. Вы уверены, что хотите изменить его? Этот токен уже добавлен @@ -198,6 +205,7 @@ Адрес аккаунта Активно Добавить + Добавить делегацию Адрес Продвинутый Все @@ -246,8 +254,10 @@ %s (и еще %s) Выберите приложение для работы с почтой Включить + Введите адрес… Введите сумму... Ошибка + Недостаточно токенов Событие EVM Адрес Ваша учетная запись будет удалена из сети после операции, так как ваш баланс опустится ниже минимального @@ -296,6 +306,7 @@ %s аккаунт отсутствует Модуль Сеть + Сеть %s не поддерживается Сети Сеть @@ -330,6 +341,8 @@ Необходимы разрешения Цена Продолжить + Прокси депозит + Отозвать доступ Подробнее Рекомендовано Обновить @@ -494,7 +507,6 @@ результаты поиска: %d Nova Wallet автоматически добавляет делегированные аккаунты (Прокси) в отдельную категорию для вас. Вы всегда можете управлять кошельками в Настройках. Обновление делегированных аккаунтов - Добавить делегацию Голоса за все время Делегат Все аккаунты @@ -534,6 +546,8 @@ Ваша делегация Ваши делегации Показывать + Вы уже делегируете полномочия этому аккаунту: %s + Делегация уже существует (BTC/ETH совместимый) ECDSA ed25519 (альтернативный) @@ -549,6 +563,7 @@ Сеть: %s\nМнемоника: %s Подождите, пока будет рассчитана комиссия Расчет комиссии в процессе + Добавить делегацию для %s стекинга Детали обмена Макс: Вы платите @@ -573,6 +588,8 @@ Ваша мнемоника недействительна Пожалуйста, убедитесь, что введенные данные содержат 64 hex символа. Сид неверный + Адрес прокси должен быть действительным адресом %s + Неверный адрес прокси Не удается декодировать QR QR код Из галереи @@ -670,6 +687,7 @@ Этот аккаунт предоставил доступ для выполнения транзакций следующему аккаунту: Более не действительны Что такое прокси? + Операции стейкинга Делегированный аккаунт %s не имеет достаточного баланса для оплаты сетевой комиссии %s. Доступный баланс для оплаты комиссии: %s Проксированные кошельки не поддерживают подпись произвольных сообщений - только транзакций %1$s не делегировал прав %2$s @@ -792,6 +810,8 @@ Вы набрали максимальное количество голосов за трек: %s Достигнуто максимальное количество голосов У вас недостаточно токенов для голосования. Доступно для голосования: %s. + Отзываемый типа доступа + Отозвать у Удалить голоса Вы ранее голосовали в референдумах в %d треке. Для того, чтобы сделать этот трек доступным для делегирования вам необходимо удалить голоса. @@ -822,6 +842,7 @@ Адрес или w3n Получатель является системным аккаунтом. Этот аккаунт не контролируется какой-либо компанией или частным лицом. \nВы уверены, что все еще хотите выполнить данный перевод? Токены будут потеряны + Выдать полномочия аккаунту Пожалуйста, убедитесь, что биометрия включена в настройках Биометрия отключена в настройках Сообщество @@ -852,11 +873,12 @@ Неподдерживаемый тип стейкинга sr25519 (рекомендованный) Schnorrkel - Добавьте учетную запись контроллера в устройство. - Нет доступа к учетной записи контроллера Выбранный аккаунт уже используется в качестве контроллера. + Делегировать полномочия (прокси) + Ваши делегации Активные делегаторы Чтобы выполнить это действие, добавьте контроллер аккаунт %s в приложение + Добавить делегацию Ваш стейк меньше минимума %s.\nСтейк меньше минимума уменьшает шансы получить вознаграждение Застейкайте ещё токенов Смените своих валидаторов. @@ -924,6 +946,8 @@ Минимальный стейк Сеть %s Застейкано + Пожалуйста, переключите свой кошелек на %s , чтобы настроить прокси. + Выберите стэш-аккаунт для настройки прокси Управление %s (макс. %s ) Достигнуто максимальное количество номинаторов. Пожалуйста, попробуйте позже @@ -931,6 +955,8 @@ Мин. стейк Вы должны добавить %s аккаунт в кошелек для того, чтобы начать стейкинг Eжемесячно + Добавьте учетную запись контроллера в устройство. + Нет доступа к учетной записи контроллера Номинировано: %s вознаграждены Один из ваших валидаторов был выбран сетью. @@ -942,6 +968,7 @@ Неактивен Ожидание следующей Эры ожидание следующей эры (%s) + У вас недостаточный баланс для прокси-депозита %s. Доступный баланс: %s Коллатор Минимальный стейк коллатора выше, чем ваша делегация. Вы не будете получать вознаграждения от этого коллатора. Информация о коллаторе @@ -1004,6 +1031,7 @@ Последний год (1Г) Ваш доступный баланс составляет %s, вам необходимо оставить %s в качестве минимального баланса и оплатить комиссию сети в размере %s. Таким образом, вы можете застейкать не более %s. + Делегированные полномочия (прокси) Текущий слот очереди Новый слот очереди Вернуть в стейк @@ -1046,7 +1074,7 @@ выбрано %d (макс. %d) Валидаторы (%d) Изменить контроллер на стэш - Сеть мигрирует в сторону замены контроллеров на прокси. Nova Wallet будет поддерживать прокси в будущих обновлениях + Используйте прокси для делегирования операций стейкинга другому аккаунту Контроллер Аккаунты Устаревают Выберите другой аккаунт в качестве контроллера, чтобы делегировать ему операции по управлению стейкингом Улучшите безопасность стейкинга diff --git a/common/src/main/res/values/attrs.xml b/common/src/main/res/values/attrs.xml index 52f0ac9382..65dcb380cc 100644 --- a/common/src/main/res/values/attrs.xml +++ b/common/src/main/res/values/attrs.xml @@ -89,8 +89,10 @@ + + diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 668c980cc4..1f3611c872 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -1,6 +1,53 @@ + Use Proxies to delegate Staking operations to another account + + Select stash account to setup proxy + Please switch your wallet to %s to setup a proxy + + %s not supported + + Revoke access type + Revoke for + + Add delegation + + Delegated authorities (proxy) + Revoke access + + Staking operations + + Delegating wallet + Delegating account + Grant access type + Delegate to + + Add delegation + + Delegation already exists + You are already delegating to this account: %s + + Invalid proxy address + Proxy address should be a valid %s address + + The deposit stays reserved on your account until the proxy is removed. + + Maximum number of proxies has been reached + You have reached the limit of %s added proxies in %s. Remove proxies to add new ones. + + Not enough tokens + You don’t have enough balance for proxy deposit of %s. Available balance: %s + + Your delegations + Add delegated authority (Proxy) + + Give authority to + Proxy deposit + Add delegation for %s staking + + Enter address… + Delegated account %s doesn’t have enough balance to pay the network fee of %s. Available balance to pay fee: %s No access to controller account Add your controller account in device. @@ -8,7 +55,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 This account granted access to perform transactions to the following account: @@ -322,7 +368,6 @@ Polkadot Vault Controller Accounts Are Being Deprecated - Network is migrating towards replacing Controllers to Proxies feature. Nova Wallet will support Proxies in future updates Update Controller to Stash No networks or tokens with entered\nname were found @@ -565,8 +610,6 @@ Organizations Individuals - Add delegation - Organization Individual 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 74abb8e5b4..3ec5adc641 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 id FROM meta_accounts WHERE status = :status") suspend fun getMetaAccountIdsByStatus(status: MetaAccountLocal.Status): List + @Query("SELECT * FROM meta_accounts WHERE status = :status") + suspend fun getMetaAccountsByStatus(status: MetaAccountLocal.Status): List + @Query("SELECT * FROM meta_accounts") suspend fun getMetaAccountsInfo(): List @@ -160,7 +163,18 @@ 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 OR parentMetaId = :metaId") + @Query( + """ + WITH RECURSIVE accounts_to_delete AS ( + SELECT id, parentMetaId FROM meta_accounts WHERE id = :metaId + UNION ALL + SELECT m.id, m.parentMetaId + FROM meta_accounts m + JOIN accounts_to_delete r ON m.parentMetaId = r.id + ) + DELETE FROM meta_accounts WHERE id IN (SELECT id FROM accounts_to_delete) + """ + ) suspend fun delete(metaId: Long) @Query("SELECT COALESCE(MAX(position), 0) + 1 FROM meta_accounts") diff --git a/feature-account-api/build.gradle b/feature-account-api/build.gradle index cd5b1d6235..9a6c4f6b2b 100644 --- a/feature-account-api/build.gradle +++ b/feature-account-api/build.gradle @@ -30,6 +30,7 @@ dependencies { implementation project(':runtime') implementation project(":common") implementation project(":feature-currency-api") + implementation project(":feature-proxy-api") implementation project(':web3names') implementation project(":feature-ledger-api") 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 9a7b95da70..7cc932d323 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 @@ -5,6 +5,6 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first -suspend fun Result>.awaitInBlock(): Result = map { +suspend fun Result>.awaitInBlock(): Result = mapCatching { it.filterIsInstance().first() } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxy.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxy.kt deleted file mode 100644 index 887e97763c..0000000000 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/model/ProxiedWithProxy.kt +++ /dev/null @@ -1,20 +0,0 @@ -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 ProxiedWithProxy( - val proxied: Proxied, - val proxy: Proxy -) { - class Proxied( - val accountId: AccountId, - val chainId: ChainId - ) - - 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 deleted file mode 100644 index 75a614bb0a..0000000000 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/ProxyRepository.kt +++ /dev/null @@ -1,14 +0,0 @@ -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 getAllProxiesForMetaAccounts(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/repository/addAccount/ledger/LedgerAddAccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/ledger/LedgerAddAccountRepository.kt index db227fa813..b51bd013f2 100644 --- 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 @@ -1,13 +1,10 @@ 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_account_api.data.repository.addAccount.AddAccountRepository 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) { +interface LedgerAddAccountRepository : AddAccountRepository { sealed interface Payload { class MetaAccount( diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/proxied/ProxiedAddAccountRepository.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/proxied/ProxiedAddAccountRepository.kt new file mode 100644 index 0000000000..dbb14c7de5 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/proxied/ProxiedAddAccountRepository.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.feature_account_api.data.repository.addAccount.proxied + +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.AddAccountRepository +import io.novafoundation.nova.feature_account_api.domain.account.identity.Identity +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +interface ProxiedAddAccountRepository : AddAccountRepository { + + class Payload( + val chainId: ChainId, + val proxiedAccountId: AccountId, + val proxyType: ProxyType, + val proxyMetaId: Long, + val identity: Identity? + ) +} 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 b23085de94..5fe2b32d9b 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 @@ -28,6 +28,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.language.Languag import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory 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.selectAddress.SelectAddressMixin import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletMixin interface AccountFeatureApi { @@ -89,4 +90,6 @@ interface AccountFeatureApi { val selectWalletMixinFactory: SelectWalletMixin.Factory val polkadotVaultVariantConfigProvider: PolkadotVaultVariantConfigProvider + + val selectAddressMixinFactory: SelectAddressMixin.Factory } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/filter/selectAddress/SelectAddressAccountFilter.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/filter/selectAddress/SelectAddressAccountFilter.kt new file mode 100644 index 0000000000..b029d55f4d --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/filter/selectAddress/SelectAddressAccountFilter.kt @@ -0,0 +1,21 @@ +package io.novafoundation.nova.feature_account_api.domain.filter.selectAddress + +import io.novafoundation.nova.common.utils.Filter +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount + +sealed interface SelectAddressAccountFilter : Filter { + + class Everything : SelectAddressAccountFilter { + + override fun shouldInclude(model: MetaAccount): Boolean { + return true + } + } + + class ExcludeMetaAccounts(val metaIds: List) : SelectAddressAccountFilter { + + override fun shouldInclude(model: MetaAccount): Boolean { + return !metaIds.contains(model.id) + } + } +} 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 3975ac8250..bfd15480ea 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,6 +4,7 @@ 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 @@ -41,6 +42,10 @@ interface AccountRepository { suspend fun accountNameFor(accountId: AccountId, chainId: ChainId): String? + suspend fun activeMetaAccounts(): List + + suspend fun allLightMetaAccounts(): List + suspend fun hasActiveMetaAccounts(): Boolean fun allMetaAccountsFlow(): 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 9ee001d004..c91204bd7b 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 @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_account_api.domain.interfaces import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.utils.Filter 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 @@ -14,9 +15,12 @@ interface MetaAccountGroupingInteractor { fun metaAccountWithTotalBalanceFlow(metaId: Long): Flow - fun getMetaAccountsForTransaction(fromId: ChainId, destinationId: ChainId): Flow> + fun getMetaAccountsWithFilter(metaAccountFilter: Filter): Flow> fun updatedProxieds(): Flow> - suspend fun hasAvailableMetaAccountsForDestination(fromId: ChainId, destinationId: ChainId): Boolean + suspend fun hasAvailableMetaAccountsForChain( + chainId: ChainId, + metaAccountFilter: Filter + ): Boolean } 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 deleted file mode 100644 index 24fbb19f44..0000000000 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/model/MetaAccountId.kt +++ /dev/null @@ -1,5 +0,0 @@ -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-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 4eb5e0c4a6..306eb1c4bf 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,5 +1,6 @@ package io.novafoundation.nova.feature_account_api.domain.model +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId class ProxyAccount( @@ -7,26 +8,4 @@ class ProxyAccount( val chainId: ChainId, val proxiedAccountId: ByteArray, val proxyType: ProxyType, -) { - - sealed class ProxyType(val name: String) { - - object Any : ProxyType("Any") - - object NonTransfer : ProxyType("NonTransfer") - - object Governance : ProxyType("Governance") - - object Staking : ProxyType("Staking") - - object IdentityJudgement : ProxyType("IdentityJudgement") - - object CancelProxy : ProxyType("CancelProxy") - - object Auction : ProxyType("Auction") - - object NominationPools : ProxyType("NominationPools") - - class Other(name: String) : ProxyType(name) - } -} +) diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/validation/HasChainAccountValidation.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/validation/HasChainAccountValidation.kt index bd14636758..2471d52faa 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/validation/HasChainAccountValidation.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/validation/HasChainAccountValidation.kt @@ -11,6 +11,7 @@ import io.novafoundation.nova.common.validation.ValidationSystemBuilder import io.novafoundation.nova.common.validation.validationError import io.novafoundation.nova.feature_account_api.R import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount.Type.LEDGER +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount.Type.PROXIED 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.asPolkadotVaultVariantOrNull @@ -32,6 +33,8 @@ interface NoChainAccountFoundError { object LedgerNotSupported : AddAccountState() class PolkadotVaultNotSupported(val variant: PolkadotVaultVariant) : AddAccountState() + + object ProxyAccountNotSupported : AddAccountState() } } @@ -53,6 +56,10 @@ class HasChainAccountValidation( errorProducer(chain, account, AddAccountState.LedgerNotSupported).validationError() } + account.type == PROXIED -> { + errorProducer(chain, account, AddAccountState.ProxyAccountNotSupported).validationError() + } + polkadotVaultVariant != null && chain.isEthereumBased -> { errorProducer(chain, account, AddAccountState.PolkadotVaultNotSupported(polkadotVaultVariant)).validationError() } @@ -91,9 +98,11 @@ fun handleChainAccountNotFound( customStyle = R.style.AccentNegativeAlertDialogTheme ) ) + AddAccountState.LedgerNotSupported -> TransformedFailure.Default( resourceManager.getString(R.string.ledger_chain_not_supported, chainName) to null ) + is AddAccountState.PolkadotVaultNotSupported -> { val vaultLabel = resourceManager.polkadotVaultLabelFor(state.variant) @@ -101,5 +110,9 @@ fun handleChainAccountNotFound( resourceManager.getString(R.string.account_parity_signer_chain_not_supported, vaultLabel, chainName) to null ) } + + AddAccountState.ProxyAccountNotSupported -> TransformedFailure.Default( + resourceManager.getString(R.string.common_network_not_supported, chainName) to null + ) } } diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/validation/NotSelfAccountValidation.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/validation/NotSelfAccountValidation.kt new file mode 100644 index 0000000000..bc70fea56a --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/domain/validation/NotSelfAccountValidation.kt @@ -0,0 +1,39 @@ +package io.novafoundation.nova.feature_account_api.domain.validation + +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.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.accountIdIn +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class NotSelfAccountValidation( + private val chainProvider: (P) -> Chain, + private val accountIdProvider: (P) -> AccountId, + private val failure: (P) -> F, + private val accountRepository: AccountRepository, +) : Validation { + + override suspend fun validate(value: P): ValidationStatus { + val chain = chainProvider(value) + val accountId = accountIdProvider(value) + val selfAccountId = accountRepository.getSelectedMetaAccount() + .accountIdIn(chain) + + val isDifferentAccounts = !accountId.contentEquals(selfAccountId) + return validOrError(isDifferentAccounts) { + failure(value) + } + } +} + +fun ValidationSystemBuilder.notSelfAccount( + chainProvider: (P) -> Chain, + accountIdProvider: (P) -> AccountId, + failure: (P) -> F, + accountRepository: AccountRepository, +) { + validate(NotSelfAccountValidation(chainProvider, accountIdProvider, failure, accountRepository)) +} 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 98f8682d84..041f4d2fad 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 @@ -2,7 +2,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.feature_proxy_api.domain.model.ProxyType import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain import java.math.BigInteger @@ -10,7 +10,7 @@ interface ProxySigningPresenter { suspend fun acknowledgeProxyOperation(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount): Boolean - suspend fun notEnoughPermission(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount, proxyTypes: List) + suspend fun notEnoughPermission(proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount, proxyTypes: List) suspend fun signingIsNotSupported() diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/wallet/list/SelectAddressCommunicator.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/wallet/list/SelectAddressCommunicator.kt deleted file mode 100644 index e8f306e02c..0000000000 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/account/wallet/list/SelectAddressCommunicator.kt +++ /dev/null @@ -1,27 +0,0 @@ -package io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list - -import android.os.Parcelable -import io.novafoundation.nova.common.navigation.InterScreenRequester -import io.novafoundation.nova.common.navigation.InterScreenResponder -import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId -import kotlinx.android.parcel.Parcelize - -interface SelectAddressForTransactionRequester : - InterScreenRequester { - - @Parcelize - class Request( - val fromChainId: ChainId, - val destinationChainId: ChainId, - val selectedAddress: String? - ) : Parcelable -} - -interface SelectAddressForTransactionResponder : - InterScreenResponder { - - @Parcelize - class Response(val selectedAddress: String) : Parcelable -} - -interface SelectAddressCommunicator : SelectAddressForTransactionRequester, SelectAddressForTransactionResponder diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/actions/CustomizableExternalActionsSheet.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/actions/CustomizableExternalActionsSheet.kt new file mode 100644 index 0000000000..e74de5f64d --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/actions/CustomizableExternalActionsSheet.kt @@ -0,0 +1,31 @@ +package io.novafoundation.nova.feature_account_api.presenatation.actions + +import android.content.Context +import android.os.Bundle +import androidx.annotation.DrawableRes +import io.novafoundation.nova.common.view.bottomSheet.list.fixed.textItem + +class ExternalActionModel( + @DrawableRes val iconRes: Int, + val title: String, + val onClick: () -> Unit +) + +class CustomizableExternalActionsSheet( + context: Context, + payload: ExternalActions.Payload, + onCopy: CopyCallback, + onViewExternal: ExternalViewCallback, + val additionalOptions: List +) : ExternalActionsSheet(context, payload, onCopy, onViewExternal) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + additionalOptions.forEach { externalActionModel -> + textItem(externalActionModel.iconRes, externalActionModel.title, showArrow = true) { + externalActionModel.onClick() + } + } + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/addressInput/AddressInputMixinUi.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/addressInput/AddressInputMixinUi.kt index bcd1eb7082..81e6b34509 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/addressInput/AddressInputMixinUi.kt +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/addressInput/AddressInputMixinUi.kt @@ -27,6 +27,9 @@ fun BaseFragment<*>.setupAddressInput( mixin.state.observe(::setState) } +/** + * Make sure that the insets are not consumed by the layer above for this method to work correctly + */ fun BaseFragment<*>.setupExternalAccounts( mixin: AddressInputMixin, inputField: AddressInputField diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressCommunicator.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressCommunicator.kt new file mode 100644 index 0000000000..87d3357935 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressCommunicator.kt @@ -0,0 +1,34 @@ +package io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress + +import android.os.Parcelable +import io.novafoundation.nova.common.navigation.InterScreenRequester +import io.novafoundation.nova.common.navigation.InterScreenResponder +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import kotlinx.android.parcel.Parcelize + +interface SelectAddressRequester : InterScreenRequester { + + @Parcelize + class Request( + val chainId: ChainId, + val selectedAddress: String?, + val filter: Filter + ) : Parcelable { + + sealed interface Filter : Parcelable { + @Parcelize + object Everything : Filter + + @Parcelize + class ExcludeMetaIds(val metaIds: List) : Filter + } + } +} + +interface SelectAddressResponder : InterScreenResponder { + + @Parcelize + class Response(val selectedAddress: String) : Parcelable +} + +interface SelectAddressCommunicator : SelectAddressRequester, SelectAddressResponder diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressMixin.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressMixin.kt new file mode 100644 index 0000000000..e307639ff0 --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressMixin.kt @@ -0,0 +1,33 @@ +package io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress + +import androidx.core.view.isInvisible +import io.novafoundation.nova.common.base.BaseFragment +import io.novafoundation.nova.common.view.YourWalletsView +import io.novafoundation.nova.feature_account_api.domain.filter.selectAddress.SelectAddressAccountFilter +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow + +interface SelectAddressMixin { + + class Payload(val chain: Chain, val filter: SelectAddressAccountFilter) + + interface Factory { + + fun create( + coroutineScope: CoroutineScope, + payloadFlow: Flow, + onAddressSelect: (String) -> Unit + ): SelectAddressMixin + } + + val isSelectAddressAvailableFlow: Flow + + suspend fun openSelectAddress(selectedAddress: String?) +} + +fun BaseFragment<*>.setupYourWalletsBtn(view: YourWalletsView, selectAddressMixin: SelectAddressMixin) { + selectAddressMixin.isSelectAddressAvailableFlow.observe { + view.isInvisible = !it + } +} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressRequestMappingExt.kt b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressRequestMappingExt.kt new file mode 100644 index 0000000000..d4449b1d7a --- /dev/null +++ b/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/presenatation/mixin/selectAddress/SelectAddressRequestMappingExt.kt @@ -0,0 +1,21 @@ +package io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress + +import io.novafoundation.nova.common.utils.Filter +import io.novafoundation.nova.feature_account_api.domain.filter.selectAddress.SelectAddressAccountFilter +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressRequester.Request + +fun Request.Filter.toMetaAccountsFilter(): SelectAddressAccountFilter { + return when (this) { + is Request.Filter.Everything -> SelectAddressAccountFilter.Everything() + + is Request.Filter.ExcludeMetaIds -> SelectAddressAccountFilter.ExcludeMetaAccounts(this.metaIds) + } +} + +fun SelectAddressAccountFilter.toRequestFilter(): Request.Filter { + return when (this) { + is SelectAddressAccountFilter.Everything -> Request.Filter.Everything + + is SelectAddressAccountFilter.ExcludeMetaAccounts -> Request.Filter.ExcludeMetaIds(this.metaIds) + } +} diff --git a/feature-account-impl/build.gradle b/feature-account-impl/build.gradle index 5909d7a896..8d1f54825b 100644 --- a/feature-account-impl/build.gradle +++ b/feature-account-impl/build.gradle @@ -43,6 +43,7 @@ dependencies { implementation project(':feature-currency-api') implementation project(':feature-ledger-api') implementation project(':feature-versions-api') + implementation project(':feature-proxy-api') implementation project(':web3names') implementation kotlinDep 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 781c97c595..ea8db504a4 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 @@ -75,6 +75,8 @@ class RealExtrinsicService( ) } + // TODO: The flow in Result may produce an exception that will be not handled since Result can't catch an exception inside a flow + // For now it's handling in awaitInBlock() extension override suspend fun submitAndWatchExtrinsic( chain: Chain, origin: TransactionOrigin, 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 60c027fea0..4855059dd1 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 @@ -22,6 +22,8 @@ import io.novafoundation.nova.feature_account_impl.presentation.common.mixin.api import io.novafoundation.nova.feature_account_impl.presentation.node.model.NodeModel import io.novafoundation.nova.feature_account_impl.presentation.view.advanced.encryption.model.CryptoTypeModel import io.novafoundation.nova.feature_account_impl.presentation.view.advanced.network.model.NetworkModel +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.feature_proxy_api.domain.model.fromString fun mapNetworkTypeToNetworkModel(networkType: NetworkType): NetworkModel { val type = when (networkType) { @@ -182,7 +184,7 @@ fun mapProxyAccountFromLocal(proxyAccountLocal: ProxyAccountLocal): ProxyAccount metaId = proxyMetaId, chainId = chainId, proxiedAccountId = proxiedAccountId, - proxyType = mapProxyTypeToString(proxyType) + proxyType = ProxyType.fromString(proxyType) ) } } @@ -207,20 +209,6 @@ fun mapOptionalNameToNameChooserState(name: String?) = when (name) { else -> AccountNameChooserMixin.State.Input(name) } -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) - } -} - private fun mapMetaAccountStateFromLocal(local: MetaAccountLocal.Status): LightMetaAccount.Status { return when (local) { MetaAccountLocal.Status.ACTIVE -> LightMetaAccount.Status.ACTIVE 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 9be3511bbf..c5eca74171 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 @@ -9,18 +9,19 @@ import io.novafoundation.nova.common.utils.mapToSet import io.novafoundation.nova.core_db.dao.MetaAccountDao 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.repository.addAccount.proxied.ProxiedAddAccountRepository 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.feature_account_impl.data.repository.addAccount.proxied.ProxiedAddAccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.hasAccountIn +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.feature_proxy_api.data.common.NestedProxiesGraphConstructor +import io.novafoundation.nova.feature_proxy_api.data.common.getAllAccountIds +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository 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 @@ -31,9 +32,22 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +private class CreateMetaAccountsResult( + val addedMetaIds: MutableList = mutableListOf(), + val alreadyExistedMetaIds: MutableList = mutableListOf(), + val shouldBeActivatedMetaIds: MutableList = mutableListOf() +) { + + fun add(other: CreateMetaAccountsResult) { + addedMetaIds.addAll(other.addedMetaIds) + alreadyExistedMetaIds.addAll(other.alreadyExistedMetaIds) + shouldBeActivatedMetaIds.addAll(other.shouldBeActivatedMetaIds) + } +} + class RealProxySyncService( private val chainRegistry: ChainRegistry, - private val proxyRepository: ProxyRepository, + private val getProxyRepository: GetProxyRepository, private val accountRepository: AccountRepository, private val accountDao: MetaAccountDao, private val identityProvider: IdentityProvider, @@ -76,28 +90,25 @@ class RealProxySyncService( Log.e(LOG_TAG, "Failed to sync proxy delegators", it) } - private suspend fun syncChainProxies(chain: Chain, metaAccounts: List) = runCatching { + private suspend fun syncChainProxies(chain: Chain, allMetaAccounts: List) = runCatching { Log.d(LOG_TAG, "Started syncing proxies for ${chain.name}") - val availableAccountIds = chain.getAvailableAccountIds(metaAccounts) + val availableAccounts = chain.getAvailableMetaAccounts(allMetaAccounts) + val availableAccountIds = availableAccounts.mapToSet { it.requireAccountIdIn(chain).intoKey() } - val proxiedsWithProxies = proxyRepository.getAllProxiesForMetaAccounts(chain.id, availableAccountIds) + val nodes = getProxyRepository.findAllProxiedsForAccounts(chain.id, availableAccountIds) + val proxiedAccountIds = NestedProxiesGraphConstructor.Node.getAllAccountIds(nodes) val oldProxies = accountDao.getProxyAccounts(chain.id) + val identities = identityProvider.identitiesFor(proxiedAccountIds, chain.id) - val notAddedProxies = filterNotAddedProxieds(proxiedsWithProxies, oldProxies) - - val identitiesByChain = notAddedProxies.loadProxiedIdentities(chain.id) - val addedProxiedsMetaIds = notAddedProxies.map { - val identity = identitiesByChain[it.proxied.accountId.intoKey()] - - proxiedAddAccountRepository.addAccount(ProxiedAddAccountRepository.Payload(it, identity)) - } + val result = createMetaAccounts(chain, oldProxies, identities, availableAccounts, nodes) - val deactivatedMetaAccountIds = getDeactivatedMetaIds(proxiedsWithProxies, oldProxies) - accountDao.changeAccountsStatus(deactivatedMetaAccountIds, MetaAccountLocal.Status.DEACTIVATED) + val deactivatedMetaIds = result.findDeactivated(oldProxies) + accountDao.changeAccountsStatus(deactivatedMetaIds, MetaAccountLocal.Status.DEACTIVATED) + accountDao.changeAccountsStatus(result.shouldBeActivatedMetaIds, MetaAccountLocal.Status.ACTIVE) - val changedMetaIds = addedProxiedsMetaIds + deactivatedMetaAccountIds + val changedMetaIds = result.addedMetaIds + deactivatedMetaIds metaAccountsUpdatesRegistry.addMetaIds(changedMetaIds) }.onFailure { Log.e(LOG_TAG, "Failed to sync proxy delegators in chain ${chain.name}", it) @@ -105,60 +116,116 @@ class RealProxySyncService( Log.d(LOG_TAG, "Finished syncing proxies for ${chain.name}") } - private fun filterNotAddedProxieds( - proxiedsWithProxies: List, - oldProxies: List - ): List { - val oldIdentifiers = oldProxies.mapToSet { it.identifier } - return proxiedsWithProxies.filter { it.toLocalIdentifier() !in oldIdentifiers } + private suspend fun createMetaAccounts( + chain: Chain, + oldProxies: List, + identities: Map, + maybeProxyMetaAccounts: List, + startNodes: List + ): CreateMetaAccountsResult { + val result = CreateMetaAccountsResult() + + val accountIdToNode = startNodes.associateBy { it.accountId } + for (metaAccount in maybeProxyMetaAccounts) { + val proxyAccountId = metaAccount.requireAccountIdIn(chain).intoKey() + val startNode = accountIdToNode[proxyAccountId] ?: continue // skip adding proxieds for metaAccount if it's not in the tree + + val nestedResult = recursivelyCreateMetaAccounts(chain.id, oldProxies, identities, metaAccount.id, startNode.nestedNodes) + result.add(nestedResult) + } + + return result } - private suspend fun getDeactivatedMetaIds( - onChainProxies: List, - oldProxies: List - ): List { - val newIdentifiers = onChainProxies.mapToSet { it.toLocalIdentifier() } - val accountsToDeactivate = oldProxies.filter { it.identifier !in newIdentifiers } - .map { it.proxiedMetaId } + private suspend fun recursivelyCreateMetaAccounts( + chainId: ChainId, + oldProxies: List, + identities: Map, + proxyMetaId: Long, + nestedNodes: List + ): CreateMetaAccountsResult { + val result = CreateMetaAccountsResult() + + for (node in nestedNodes) { + val maybeExistedProxiedMetaId = node.getExistedProxiedMetaId(chainId, oldProxies, proxyMetaId) + + var nextMetaId = if (maybeExistedProxiedMetaId == null) { + val newMetaId = addProxiedAccount(chainId, node, proxyMetaId, identities) + result.addedMetaIds.add(newMetaId) + newMetaId + } else { + // An account may be deactivated but not deleted yet in case when we remove a proxy and then add it again + // To support this case we should track deactivated accounts and activate them again + val existedMetaAccount = accountRepository.getMetaAccount(maybeExistedProxiedMetaId) + if (existedMetaAccount.status == LightMetaAccount.Status.DEACTIVATED) { + result.shouldBeActivatedMetaIds.add(maybeExistedProxiedMetaId) + } + + result.alreadyExistedMetaIds.add(maybeExistedProxiedMetaId) + maybeExistedProxiedMetaId + } - return accountsToDeactivate.takeNotYetDeactivatedMetaAccounts() - } + if (node.nestedNodes.isNotEmpty()) { + val nestedResult = recursivelyCreateMetaAccounts(chainId, oldProxies, identities, nextMetaId, node.nestedNodes) + result.add(nestedResult) + } + } - private suspend fun getMetaAccounts(): List { - return accountRepository.getActiveMetaAccounts() - .filter { it.isAllowedToSyncProxy() } + return result } - private fun MetaAccount.isAllowedToSyncProxy(): Boolean { - return when (type) { - LightMetaAccount.Type.SECRETS, - LightMetaAccount.Type.PARITY_SIGNER, - LightMetaAccount.Type.LEDGER, - LightMetaAccount.Type.POLKADOT_VAULT -> true + private suspend fun addProxiedAccount( + chainId: ChainId, + node: NestedProxiesGraphConstructor.Node, + metaId: Long, + identities: Map + ) = proxiedAddAccountRepository.addAccount( + ProxiedAddAccountRepository.Payload( + chainId = chainId, + proxiedAccountId = node.accountId.value, + proxyType = node.permissionType, + proxyMetaId = metaId, + identity = identities[node.accountId] + ) + ) + + private fun NestedProxiesGraphConstructor.Node.getExistedProxiedMetaId( + chainId: ChainId, + oldProxies: List, + proxyMetaId: Long + ): Long? { + val oldIdentifiers = oldProxies.associateBy { it.identifier } + + val identifier = ProxyAccountLocal.makeIdentifier( + proxyMetaId = proxyMetaId, + chainId = chainId, + proxiedAccountId = accountId.value, + proxyType = permissionType.name + ) - LightMetaAccount.Type.WATCH_ONLY -> shouldSyncWatchOnlyProxies + return oldIdentifiers[identifier]?.proxiedMetaId + } - LightMetaAccount.Type.PROXIED -> false - } + private suspend fun getMetaAccounts(): List { + return accountRepository.getActiveMetaAccounts() + .filter { it.isAllowedToSyncProxy(shouldSyncWatchOnlyProxies) } } private suspend fun getSupportedProxyChains(): List { return chainRegistry.findChains { it.supportProxy } } - private fun Chain.getAvailableAccountIds(metaAccounts: List): List { - return metaAccounts.mapNotNull { metaAccount -> - val accountId = metaAccount.accountIdIn(chain = this) - accountId?.let { - MetaAccountId(accountId, metaAccount.id) - } - } + private fun Chain.getAvailableMetaAccounts(metaAccounts: List): List { + return metaAccounts.filter { metaAccount -> metaAccount.hasAccountIn(chain = this) } } - private suspend fun List.loadProxiedIdentities(chainId: ChainId): Map { - val proxiedAccountIds = map { it.proxied.accountId } + private suspend fun CreateMetaAccountsResult.findDeactivated(oldProxies: List): List { + val oldIds = oldProxies.map { it.proxiedMetaId } + val deactivated = oldIds - alreadyExistedMetaIds + + val alreadyDeactivatedMetaIdsInCache = accountDao.getMetaAccountIdsByStatus(MetaAccountLocal.Status.DEACTIVATED) - return identityProvider.identitiesFor(proxiedAccountIds, chainId) + return deactivated - alreadyDeactivatedMetaIdsInCache.toSet() } private suspend fun List.takeNotYetDeactivatedMetaAccounts(): List { @@ -166,13 +233,4 @@ class RealProxySyncService( return this - alreadyDeactivatedMetaAccountIds.toSet() } - - private fun ProxiedWithProxy.toLocalIdentifier(): String { - return ProxyAccountLocal.makeIdentifier( - 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/proxy/SyncServiceExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/SyncServiceExt.kt new file mode 100644 index 0000000000..07d04c07d0 --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/proxy/SyncServiceExt.kt @@ -0,0 +1,17 @@ +package io.novafoundation.nova.feature_account_impl.data.proxy + +import io.novafoundation.nova.feature_account_api.domain.model.LightMetaAccount +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount + +fun MetaAccount.isAllowedToSyncProxy(shouldSyncWatchOnly: Boolean): Boolean { + return when (type) { + LightMetaAccount.Type.SECRETS, + LightMetaAccount.Type.PARITY_SIGNER, + LightMetaAccount.Type.LEDGER, + LightMetaAccount.Type.POLKADOT_VAULT -> true + + LightMetaAccount.Type.WATCH_ONLY -> shouldSyncWatchOnly + + LightMetaAccount.Type.PROXIED -> 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 cef2adf9ca..0d2375bed3 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,6 +18,7 @@ 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 @@ -121,6 +122,14 @@ class AccountRepositoryImpl( return accountDataSource.accountNameFor(accountId, chainId) } + override suspend fun activeMetaAccounts(): List { + return accountDataSource.activeMetaAccounts() + } + + override suspend fun allLightMetaAccounts(): List { + return accountDataSource.allLightMetaAccounts() + } + override suspend fun hasActiveMetaAccounts(): Boolean { return accountDataSource.hasActiveMetaAccounts() } 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 9259474815..e69de29bb2 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,133 +0,0 @@ -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.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 -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, - val proxyType: String, - val delay: BigInteger -) - -class RealProxyRepository( - private val remoteSource: StorageDataSource, - private val chainRegistry: ChainRegistry -) : ProxyRepository { - - 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 notDelayedProxies = proxies.filter { it.delay == BigInteger.ZERO } - val matchedProxies = matchProxiesToAccountsAndMap(notDelayedProxies, accountIdToMetaAccounts) - - if (matchedProxies.isEmpty()) return@mapNotNull null - - delegator to matchedProxies - }.flatMap { (delegator, proxies) -> - proxies.map { proxy -> mapToProxiedWithProxies(chainId, delegator, proxy) } - } - } - - 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.accountId == proxyAccountId.intoKey() } - .map { mapProxyTypeToString(it.proxyType) } - } - - private suspend fun receiveAllProxies(chainId: ChainId): Map> { - return remoteSource.query(chainId) { - runtime.metadata.module(Modules.PROXY) - .storage("Proxies") - .entries( - keyExtractor = { (accountId: AccountId) -> AccountIdKey(accountId) }, - binding = { result, _ -> - bindProxyAccounts(result) - }, - recover = { _, _ -> - // Do nothing if entry binding throws an exception - } - ) - } - } - - private fun bindProxyAccounts(dynamicInstance: Any?): List { - if (dynamicInstance == null) return emptyList() - - 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() - val delay = proxy.getTyped("delay") - OnChainProxyModel( - proxyAccountId.intoKey(), - proxyType.name, - delay - ) - } - } - - private fun mapToProxiedWithProxies( - chainId: ChainId, - delegator: AccountIdKey, - proxy: ProxiedWithProxy.Proxy - ): ProxiedWithProxy { - return ProxiedWithProxy( - proxied = ProxiedWithProxy.Proxied( - accountId = delegator.value, - chainId = chainId - ), - proxy = proxy - ) - } - - private fun matchProxiesToAccountsAndMap( - proxies: List, - accountIdToMetaAccounts: Map> - ): List { - return proxies.flatMap { onChainProxy -> - val matchedAccounts = accountIdToMetaAccounts[onChainProxy.accountId] ?: return@flatMap emptyList() - - matchedAccounts.map { - ProxiedWithProxy.Proxy( - accountId = onChainProxy.accountId.value, - metaId = it.metaId, - proxyType = onChainProxy.proxyType - ) - } - } - } -} diff --git a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/BaseAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/BaseAddAccountRepository.kt similarity index 72% rename from feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/BaseAddAccountRepository.kt rename to feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/BaseAddAccountRepository.kt index 0c3b6a7a6a..b2467f2e36 100644 --- a/feature-account-api/src/main/java/io/novafoundation/nova/feature_account_api/data/repository/addAccount/BaseAddAccountRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/BaseAddAccountRepository.kt @@ -1,6 +1,7 @@ -package io.novafoundation.nova.feature_account_api.data.repository.addAccount +package io.novafoundation.nova.feature_account_impl.data.repository.addAccount import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.AddAccountRepository abstract class BaseAddAccountRepository( private val proxySyncService: ProxySyncService 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 index 949ea9a4d7..db8a06b083 100644 --- 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 @@ -6,7 +6,9 @@ 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_impl.data.repository.addAccount.BaseAddAccountRepository import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.ledger.LedgerAddAccountRepository.Payload import io.novafoundation.nova.feature_ledger_api.data.repository.LedgerDerivationPath import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -16,7 +18,7 @@ class RealLedgerAddAccountRepository( private val chainRegistry: ChainRegistry, private val secretStoreV2: SecretStoreV2, private val proxySyncService: ProxySyncService, -) : LedgerAddAccountRepository(proxySyncService) { +) : BaseAddAccountRepository(proxySyncService), LedgerAddAccountRepository { override suspend fun addAccountInternal(payload: Payload): Long { return when (payload) { diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/paritySigner/ParitySignerAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/paritySigner/ParitySignerAddAccountRepository.kt index 77f6a49249..c9caff9060 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/paritySigner/ParitySignerAddAccountRepository.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/paritySigner/ParitySignerAddAccountRepository.kt @@ -5,7 +5,7 @@ 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.feature_account_impl.data.repository.addAccount.BaseAddAccountRepository import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry 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/addAccount/proxied/ProxiedAddAccountRepository.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/proxied/RealProxiedAddAccountRepository.kt similarity index 66% rename from feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/proxied/ProxiedAddAccountRepository.kt rename to feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/addAccount/proxied/RealProxiedAddAccountRepository.kt index 29365870bf..0145ab81bf 100644 --- 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/RealProxiedAddAccountRepository.kt @@ -4,9 +4,8 @@ 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.feature_account_api.data.repository.addAccount.proxied.ProxiedAddAccountRepository +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.proxied.ProxiedAddAccountRepository.Payload import io.novafoundation.nova.runtime.ext.addressOf import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -14,15 +13,10 @@ 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( +class RealProxiedAddAccountRepository( private val accountDao: MetaAccountDao, private val chainRegistry: ChainRegistry -) : AddAccountRepository { - - class Payload( - val proxiedWithProxy: ProxiedWithProxy, - val identity: Identity? - ) +) : ProxiedAddAccountRepository { override suspend fun addAccount(payload: Payload): Long { val position = accountDao.nextAccountPosition() @@ -38,9 +32,7 @@ class ProxiedAddAccountRepository( payload: Payload, position: Int ): MetaAccountLocal { - val proxied = payload.proxiedWithProxy.proxied - val proxy = payload.proxiedWithProxy.proxy - val chain = chainRegistry.getChain(proxied.chainId) + val chain = chainRegistry.getChain(payload.chainId) return MetaAccountLocal( substratePublicKey = null, @@ -48,8 +40,8 @@ class ProxiedAddAccountRepository( substrateAccountId = null, ethereumPublicKey = null, ethereumAddress = null, - name = payload.identity?.name ?: chain.addressOf(proxied.accountId), - parentMetaId = proxy.metaId, + name = payload.identity?.name ?: chain.addressOf(payload.proxiedAccountId), + parentMetaId = payload.proxyMetaId, isSelected = false, position = position, type = MetaAccountLocal.Type.PROXIED, @@ -58,13 +50,11 @@ class ProxiedAddAccountRepository( } private fun createChainAccount(proxiedMetaId: Long, payload: Payload): ChainAccountLocal { - val proxied = payload.proxiedWithProxy.proxied - return ChainAccountLocal( metaId = proxiedMetaId, - chainId = proxied.chainId, + chainId = payload.chainId, publicKey = null, - accountId = proxied.accountId, + accountId = payload.proxiedAccountId, cryptoType = null ) } @@ -73,15 +63,12 @@ class ProxiedAddAccountRepository( 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 + proxyMetaId = payload.proxyMetaId, + chainId = payload.chainId, + proxiedAccountId = payload.proxiedAccountId, + proxyType = payload.proxyType.name ) } } 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 index b7ff9af903..9c710fe5cc 100644 --- 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 @@ -3,7 +3,7 @@ package io.novafoundation.nova.feature_account_impl.data.repository.addAccount.s 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_impl.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 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 index ac772a3dbb..e27f5400e9 100644 --- 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 @@ -4,7 +4,7 @@ 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.feature_account_impl.data.repository.addAccount.BaseAddAccountRepository 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/AccountDataSource.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/repository/datasource/AccountDataSource.kt index 2cd361e60d..aa88047d57 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,6 +45,8 @@ interface AccountDataSource : SecretStoreV1 { suspend fun accountNameFor(accountId: AccountId, chainId: ChainId): String? + suspend fun activeMetaAccounts(): List + suspend fun allLightMetaAccounts(): List fun allMetaAccountsFlow(): 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 36030a9cd4..7ea0c83fe5 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 @@ -147,6 +147,11 @@ class AccountDataSourceImpl( .map(::mapMetaAccountLocalToMetaAccount) } + override suspend fun activeMetaAccounts(): List { + return metaAccountDao.getMetaAccountsByStatus(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/data/signer/LeafSigner.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/LeafSigner.kt index 0cc896cc2c..334f5dff48 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 @@ -5,6 +5,7 @@ import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdI 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.SignerPayloadExtrinsic abstract class LeafSigner( private val metaAccount: MetaAccount, @@ -13,4 +14,8 @@ abstract class LeafSigner( override suspend fun signerAccountId(chain: Chain): AccountId { return metaAccount.requireAccountIdIn(chain) } + + override suspend fun modifyPayload(payloadExtrinsic: SignerPayloadExtrinsic): SignerPayloadExtrinsic { + return payloadExtrinsic + } } 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..e69de29bb2 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 80b247b602..6713f92d23 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 @@ -4,8 +4,8 @@ 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.feature_proxy_api.domain.model.ProxyType 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 @@ -49,7 +49,7 @@ class ProxiedFeeSigner( val modifiedPayloadExtrinsic = payloadExtrinsic.wrapIntoProxyPayload( proxyAccountId = getProxyAccountId(), currentProxyNonce = BigInteger.ZERO, - proxyType = ProxyAccount.ProxyType.Any, + proxyType = ProxyType.Any, callInstance = 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 745d2ba107..f4cc427837 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,17 +4,17 @@ 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 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.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.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType import io.novafoundation.nova.runtime.ext.commissionAsset import io.novafoundation.nova.runtime.extrinsic.signer.NovaSigner import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @@ -31,7 +31,7 @@ class ProxiedSignerFactory( private val chainRegistry: ChainRegistry, private val accountRepository: AccountRepository, private val proxySigningPresenter: ProxySigningPresenter, - private val proxyRepository: ProxyRepository, + private val getProxyRepository: GetProxyRepository, private val rpcCalls: RpcCalls, private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus, private val proxyCallFilterFactory: ProxyCallFilterFactory @@ -44,7 +44,7 @@ class ProxiedSignerFactory( accountRepository = accountRepository, signerProvider = signerProvider, proxySigningPresenter = proxySigningPresenter, - proxyRepository = proxyRepository, + getProxyRepository = getProxyRepository, rpcCalls = rpcCalls, proxyExtrinsicValidationEventBus = proxyExtrinsicValidationEventBus, isRootProxied = isRoot, @@ -59,7 +59,7 @@ class ProxiedSigner( private val accountRepository: AccountRepository, private val signerProvider: SignerProvider, private val proxySigningPresenter: ProxySigningPresenter, - private val proxyRepository: ProxyRepository, + private val getProxyRepository: GetProxyRepository, private val rpcCalls: RpcCalls, private val proxyExtrinsicValidationEventBus: ProxyExtrinsicValidationRequestBus, private val isRootProxied: Boolean, @@ -73,27 +73,30 @@ class ProxiedSigner( return delegate.signerAccountId(chain) } + override suspend fun modifyPayload(payloadExtrinsic: SignerPayloadExtrinsic): SignerPayloadExtrinsic { + val chain = chainRegistry.getChain(payloadExtrinsic.chainId) + val proxyMetaAccount = getProxyMetaAccount() + val delegate = createDelegate(proxyMetaAccount) + val payload = checkPermissionAndWrap(proxyMetaAccount, payloadExtrinsic, chain) + return delegate.modifyPayload(payload) + } + override suspend fun signExtrinsic(payloadExtrinsic: SignerPayloadExtrinsic): SignedExtrinsic { val chain = chainRegistry.getChain(payloadExtrinsic.chainId) val proxyMetaAccount = getProxyMetaAccount() - acknowledgeProxyOperation(proxyMetaAccount) + if (isRootProxied) { + 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) + val payloadToSign = if (isRootProxied) modifyPayload(payloadExtrinsic) else payloadExtrinsic if (isRootProxied) { - validateExtrinsic(modifiedPayload, chain) + validateExtrinsic(payloadToSign, chain) } val delegate = createDelegate(proxyMetaAccount) - - val signedExtrinsic = delegate.signExtrinsic(modifiedPayload) - return signedExtrinsic + return delegate.signExtrinsic(payloadToSign) } override suspend fun signRaw(payload: SignerPayloadRaw): SignedRaw { @@ -129,11 +132,11 @@ class ProxiedSigner( } } - private suspend fun modifyPayload(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic, chain: Chain): SignerPayloadExtrinsic { + private suspend fun checkPermissionAndWrap(proxyMetaAccount: MetaAccount, payload: SignerPayloadExtrinsic, chain: Chain): SignerPayloadExtrinsic { val proxyAccountId = proxyMetaAccount.requireAccountIdIn(chain) val proxiedAccountId = proxiedMetaAccount.requireAccountIdIn(chain) - val availableProxyTypes = proxyRepository.getDelegatedProxyTypes( + val availableProxyTypes = getProxyRepository.getDelegatedProxyTypesRemote( chainId = payload.chainId, proxiedAccountId = proxiedAccountId, proxyAccountId = proxyAccountId 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 df6404d7cb..8d1638787d 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 @@ -1,21 +1,21 @@ 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.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 io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType import jp.co.soramitsu.fearless_utils.runtime.definitions.types.generics.GenericCall class ProxyCallFilterFactory { - fun getCallFilterFor(proxyType: ProxyAccount.ProxyType): CallFilter { + fun getCallFilterFor(proxyType: ProxyType): CallFilter { return when (proxyType) { - ProxyAccount.ProxyType.Any, - is ProxyAccount.ProxyType.Other -> EverythingFilter() + ProxyType.Any, + is ProxyType.Other -> EverythingFilter() - ProxyAccount.ProxyType.NonTransfer -> AnyOfCallFilter( + 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 -> AnyOfCallFilter( + ProxyType.Governance -> AnyOfCallFilter( WhiteListFilter(Modules.TREASURY), WhiteListFilter(Modules.BOUNTIES), WhiteListFilter(Modules.UTILITY), @@ -56,7 +56,7 @@ class ProxyCallFilterFactory { WhiteListFilter(Modules.WHITELIST) ) - ProxyAccount.ProxyType.Staking -> AnyOfCallFilter( + 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 -> AnyOfCallFilter( + ProxyType.NominationPools -> AnyOfCallFilter( WhiteListFilter(Modules.NOMINATION_POOLS), WhiteListFilter(Modules.UTILITY) ) - ProxyAccount.ProxyType.IdentityJudgement -> AnyOfCallFilter( + ProxyType.IdentityJudgement -> AnyOfCallFilter( WhiteListFilter(Modules.IDENTITY, listOf("provide_judgement")), WhiteListFilter(Modules.UTILITY) ) - ProxyAccount.ProxyType.CancelProxy -> WhiteListFilter(Modules.PROXY, listOf("reject_announcement")) + ProxyType.CancelProxy -> WhiteListFilter(Modules.PROXY, listOf("reject_announcement")) - ProxyAccount.ProxyType.Auction -> AnyOfCallFilter( + ProxyType.Auction -> AnyOfCallFilter( WhiteListFilter(Modules.AUCTIONS), WhiteListFilter(Modules.CROWDLOAN), WhiteListFilter(Modules.REGISTRAR), @@ -87,7 +87,7 @@ class ProxyCallFilterFactory { } } -fun ProxyCallFilterFactory.getFirstMatchedTypeOrNull(call: GenericCall.Instance, proxyTypes: List): ProxyAccount.ProxyType? { +fun ProxyCallFilterFactory.getFirstMatchedTypeOrNull(call: GenericCall.Instance, proxyTypes: List): 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/SignerPayloadModifierExt.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/data/signer/proxy/SignerPayloadModifierExt.kt index 12ede274f2..8aa9eda85d 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 @@ -1,7 +1,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_proxy_api.domain.model.ProxyType import io.novafoundation.nova.runtime.extrinsic.multi.SimpleCallBuilder import jp.co.soramitsu.fearless_utils.runtime.AccountId import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot @@ -17,7 +17,7 @@ import java.math.BigInteger fun SignerPayloadExtrinsic.wrapIntoProxyPayload( proxyAccountId: AccountId, currentProxyNonce: BigInteger, - proxyType: ProxyAccount.ProxyType, + proxyType: ProxyType, callInstance: CallRepresentation.Instance ): SignerPayloadExtrinsic { val proxiedAccountId = accountId 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..5b9bb6a53d 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 @@ -7,7 +7,7 @@ import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationCommunicator import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator import io.novafoundation.nova.feature_account_impl.data.signer.paritySigner.PolkadotVaultVariantSignCommunicator @@ -42,6 +42,7 @@ import io.novafoundation.nova.feature_account_impl.presentation.pincode.di.PinCo import io.novafoundation.nova.feature_account_impl.presentation.watchOnly.change.di.ChangeWatchAccountComponent 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_proxy_api.di.ProxyFeatureApi import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi 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, + ProxyFeatureApi::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 8848ee5d22..d9d1ae5c7c 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,7 @@ import io.novafoundation.nova.core_db.dao.MetaAccountDao 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_proxy_api.data.repository.GetProxyRepository import io.novafoundation.nova.feature_versions_api.domain.UpdateNotificationsInteractor import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory @@ -118,6 +119,8 @@ interface AccountFeatureDependencies { fun computationalCache(): ComputationalCache + fun getProxyRepository(): GetProxyRepository + 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..fa69dc6961 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 @@ -5,12 +5,13 @@ import io.novafoundation.nova.common.di.FeatureContainer import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.common.sequrity.verification.PinCodeTwoFactorVerificationCommunicator import io.novafoundation.nova.core_db.di.DbApi -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectWallet.SelectWalletCommunicator import io.novafoundation.nova.feature_account_api.presenatation.sign.LedgerSignCommunicator 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_currency_api.di.CurrencyFeatureApi +import io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi import io.novafoundation.nova.feature_versions_api.di.VersionsFeatureApi import io.novafoundation.nova.runtime.di.RuntimeApi import io.novafoundation.nova.web3names.di.Web3NamesApi @@ -33,6 +34,7 @@ class AccountFeatureHolder @Inject constructor( .dbApi(getFeature(DbApi::class.java)) .runtimeApi(getFeature(RuntimeApi::class.java)) .currencyFeatureApi(getFeature(CurrencyFeatureApi::class.java)) + .proxyFeatureApi(getFeature(ProxyFeatureApi::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 1741d43d12..5d12bfd469 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 @@ -29,11 +29,8 @@ import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicServic 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 +import io.novafoundation.nova.feature_account_api.data.repository.addAccount.proxied.ProxiedAddAccountRepository 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 @@ -61,8 +58,6 @@ import io.novafoundation.nova.feature_account_impl.data.proxy.RealMetaAccountsUp 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 @@ -81,12 +76,18 @@ import io.novafoundation.nova.feature_account_impl.domain.MetaAccountGroupingInt 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_api.presenatation.mixin.selectAddress.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin 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 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.mixin.SelectAddressMixinFactory 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 @@ -97,6 +98,7 @@ import io.novafoundation.nova.feature_account_impl.presentation.mixin.identity.R import io.novafoundation.nova.feature_account_impl.presentation.mixin.selectWallet.RealRealSelectWalletMixinFactory 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.feature_proxy_api.data.repository.GetProxyRepository import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE import io.novafoundation.nova.runtime.ethereum.gas.GasPriceProviderFactory import io.novafoundation.nova.runtime.extrinsic.ExtrinsicBuilderFactory @@ -131,36 +133,6 @@ class AccountFeatureModule { preferences: Preferences ): MetaAccountsUpdatesRegistry = RealMetaAccountsUpdatesRegistry(preferences) - @Provides - @FeatureScope - fun provideProxyRepository( - @Named(REMOTE_STORAGE_SOURCE) storageDataSource: StorageDataSource, - chainRegistry: ChainRegistry - ): ProxyRepository = RealProxyRepository(storageDataSource, chainRegistry) - - @Provides - @FeatureScope - fun provideProxySyncService( - chainRegistry: ChainRegistry, - proxyRepository: ProxyRepository, - accounRepository: AccountRepository, - metaAccountDao: MetaAccountDao, - @OnChainIdentity identityProvider: IdentityProvider, - metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, - proxiedAddAccountRepository: ProxiedAddAccountRepository, - rootScope: RootScope - ): ProxySyncService = RealProxySyncService( - chainRegistry = chainRegistry, - proxyRepository = proxyRepository, - accountRepository = accounRepository, - accountDao = metaAccountDao, - identityProvider = identityProvider, - metaAccountsUpdatesRegistry = metaAccountsUpdatesRegistry, - proxiedAddAccountRepository = proxiedAddAccountRepository, - rootScope = rootScope, - shouldSyncWatchOnlyProxies = BuildConfig.DEBUG - ) - @Provides @FeatureScope fun provideEncryptionDefaults(): EncryptionDefaults = EncryptionDefaults( @@ -508,4 +480,39 @@ class AccountFeatureModule { fun provideSigningNotSupportedPresentable( contextManager: ContextManager ): SigningNotSupportedPresentable = RealSigningNotSupportedPresentable(contextManager) + + @Provides + @FeatureScope + fun provideProxySyncService( + chainRegistry: ChainRegistry, + getProxyRepository: GetProxyRepository, + accounRepository: AccountRepository, + metaAccountDao: MetaAccountDao, + @OnChainIdentity identityProvider: IdentityProvider, + metaAccountsUpdatesRegistry: MetaAccountsUpdatesRegistry, + proxiedAddAccountRepository: ProxiedAddAccountRepository, + rootScope: RootScope + ): ProxySyncService = RealProxySyncService( + chainRegistry, + getProxyRepository, + accounRepository, + metaAccountDao, + identityProvider, + metaAccountsUpdatesRegistry, + proxiedAddAccountRepository, + rootScope, + shouldSyncWatchOnlyProxies = BuildConfig.DEBUG + ) + + @Provides + @FeatureScope + fun provideSelectAddressMixinFactory( + selectAddressCommunicator: SelectAddressCommunicator, + metaAccountGroupingInteractor: MetaAccountGroupingInteractor, + ): SelectAddressMixin.Factory { + return SelectAddressMixinFactory( + selectAddressCommunicator, + metaAccountGroupingInteractor + ) + } } 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 index ac8cc8c5f8..ad738bf6be 100644 --- 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 @@ -7,11 +7,12 @@ 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_api.data.repository.addAccount.proxied.ProxiedAddAccountRepository 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.proxied.RealProxiedAddAccountRepository 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 @@ -93,7 +94,7 @@ class AddAccountsModule { fun provideProxiedAddAccountRepository( accountDao: MetaAccountDao, chainRegistry: ChainRegistry - ) = ProxiedAddAccountRepository( + ): ProxiedAddAccountRepository = RealProxiedAddAccountRepository( accountDao, chainRegistry ) diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/IdentityProviderModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/IdentityProviderModule.kt index 61d88e2056..f39c44dfb8 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/IdentityProviderModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/di/modules/IdentityProviderModule.kt @@ -9,6 +9,7 @@ import io.novafoundation.nova.feature_account_api.domain.account.identity.OnChai import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_impl.domain.account.identity.LocalIdentityProvider import io.novafoundation.nova.feature_account_impl.domain.account.identity.OnChainIdentityProvider +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry @Module class IdentityProviderModule { @@ -16,9 +17,10 @@ class IdentityProviderModule { @Provides @LocalIdentity fun provideLocalIdentityProvider( - accountRepository: AccountRepository + accountRepository: AccountRepository, + chainRegistry: ChainRegistry ): IdentityProvider { - return LocalIdentityProvider(accountRepository) + return LocalIdentityProvider(accountRepository, chainRegistry) } @Provides 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 a2e0c136c4..f28a803fe5 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 @@ -3,12 +3,12 @@ package io.novafoundation.nova.feature_account_impl.di.modules.signers import dagger.Module import dagger.Provides 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 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.feature_proxy_api.data.repository.GetProxyRepository import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry import io.novafoundation.nova.runtime.network.rpc.RpcCalls @@ -29,7 +29,7 @@ class ProxiedSignerModule { chainRegistry: ChainRegistry, accountRepository: AccountRepository, proxySigningPresenter: ProxySigningPresenter, - proxyRepository: ProxyRepository, + proxyRepository: GetProxyRepository, rpcCalls: RpcCalls, proxyExtrinsicValidationRequestBus: ProxyExtrinsicValidationRequestBus, proxyCallFilterFactory: ProxyCallFilterFactory 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 e9ed2268c5..696662d29e 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 @@ -1,10 +1,11 @@ package io.novafoundation.nova.feature_account_impl.domain import io.novafoundation.nova.common.list.GroupedList +import io.novafoundation.nova.common.utils.Filter import io.novafoundation.nova.common.utils.amountFromPlanks +import io.novafoundation.nova.common.utils.applyFilter 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 @@ -14,7 +15,6 @@ 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 import io.novafoundation.nova.feature_currency_api.domain.model.Currency @@ -63,10 +63,10 @@ class MetaAccountGroupingInteractorImpl( } } - override fun getMetaAccountsForTransaction(fromId: ChainId, destinationId: ChainId): Flow> = flowOf { - val fromChain = chainRegistry.getChain(fromId) - val destinationChain = chainRegistry.getChain(destinationId) - getValidMetaAccountsForTransaction(fromChain, destinationChain) + override fun getMetaAccountsWithFilter( + metaAccountFilter: Filter + ): Flow> = flowOf { + getValidMetaAccountsForTransaction(metaAccountFilter) .groupBy(MetaAccount::type) .toSortedMap(metaAccountTypeComparator()) } @@ -93,11 +93,13 @@ class MetaAccountGroupingInteractorImpl( } } - override suspend fun hasAvailableMetaAccountsForDestination(fromId: ChainId, destinationId: ChainId): Boolean { - val fromChain = chainRegistry.getChain(fromId) - val destinationChain = chainRegistry.getChain(destinationId) - return getValidMetaAccountsForTransaction(fromChain, destinationChain) - .any { it.hasAccountIn(destinationChain) } + override suspend fun hasAvailableMetaAccountsForChain( + chainId: ChainId, + metaAccountFilter: Filter + ): Boolean { + val chain = chainRegistry.getChain(chainId) + return getValidMetaAccountsForTransaction(metaAccountFilter) + .any { it.hasAccountIn(chain) } } private suspend fun metaAccountWithTotalBalance( @@ -126,11 +128,9 @@ class MetaAccountGroupingInteractorImpl( ) } - private suspend fun getValidMetaAccountsForTransaction(from: Chain, destination: Chain): List { - val selectedMetaAccount = accountRepository.getSelectedMetaAccount() - val fromChainAddress = selectedMetaAccount.addressIn(from) + private suspend fun getValidMetaAccountsForTransaction(metaAccountFilter: Filter): List { return accountRepository.getActiveMetaAccounts() - .removed { fromChainAddress == it.addressIn(destination) } + .applyFilter(metaAccountFilter) .filter { when (it.type) { LightMetaAccount.Type.SECRETS, 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 df37cdfd74..95ce1009d1 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 @@ -1,15 +1,21 @@ package io.novafoundation.nova.feature_account_impl.domain.account.identity +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.common.address.intoKey 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.hasAccountIn +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry 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 class LocalIdentityProvider( - private val accountRepository: AccountRepository + private val accountRepository: AccountRepository, + private val chainRegistry: ChainRegistry ) : IdentityProvider { override suspend fun identityFor(accountId: AccountId, chainId: ChainId): Identity? = withContext(Dispatchers.IO) { @@ -17,4 +23,16 @@ class LocalIdentityProvider( name?.let(::Identity) } + + override suspend fun identitiesFor(accountIds: Collection, chainId: ChainId): Map { + val chain = chainRegistry.getChain(chainId) + val metaAccounts = accountRepository.activeMetaAccounts() + .filter { it.hasAccountIn(chain) } + .associateBy { it.requireAccountIdIn(chain).intoKey() } + + return accountIds.associateBy { it.intoKey() } + .mapValues { (key, _) -> + metaAccounts[key]?.name?.let(::Identity) + } + } } 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 9c6645ba91..5828135830 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 @@ -2,6 +2,7 @@ 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.Filter import io.novafoundation.nova.common.utils.WithCoroutineScopeExtensions import io.novafoundation.nova.common.utils.lazyAsync import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor @@ -25,19 +26,19 @@ class MetaAccountValidForTransactionListingMixinFactory( fun create( coroutineScope: CoroutineScope, - fromChainId: ChainId, - destinationChainId: ChainId, - selectedAddress: String? + chainId: ChainId, + selectedAddress: String?, + metaAccountFilter: Filter ): MetaAccountListingMixin { return MetaAccountValidForTransactionListingMixin( walletUiUseCase = walletUiUseCase, resourceManager = resourceManager, metaAccountGroupingInteractor = metaAccountGroupingInteractor, chainRegistry = chainRegistry, - fromChainId = fromChainId, - destinationChainId = destinationChainId, + chainId = chainId, selectedAddress = selectedAddress, accountTypePresentationMapper = accountTypePresentationMapper, + metaAccountFilter = metaAccountFilter, coroutineScope = coroutineScope ) } @@ -48,16 +49,16 @@ private class MetaAccountValidForTransactionListingMixin( private val resourceManager: ResourceManager, private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, private val chainRegistry: ChainRegistry, - private val fromChainId: ChainId, - private val destinationChainId: ChainId, + private val chainId: ChainId, private val selectedAddress: String?, private val accountTypePresentationMapper: MetaAccountTypePresentationMapper, + private val metaAccountFilter: Filter, coroutineScope: CoroutineScope, ) : MetaAccountListingMixin, WithCoroutineScopeExtensions by WithCoroutineScopeExtensions(coroutineScope) { - private val destinationChainFlow by coroutineScope.lazyAsync { chainRegistry.getChain(destinationChainId) } + private val chainFlow by coroutineScope.lazyAsync { chainRegistry.getChain(chainId) } - override val metaAccountsFlow = metaAccountGroupingInteractor.getMetaAccountsForTransaction(fromChainId, destinationChainId) + override val metaAccountsFlow = metaAccountGroupingInteractor.getMetaAccountsWithFilter(metaAccountFilter) .map { list -> list.toListWithHeaders( keyMapper = { type, _ -> accountTypePresentationMapper.mapMetaAccountTypeToUi(type) }, @@ -69,7 +70,7 @@ private class MetaAccountValidForTransactionListingMixin( private suspend fun mapMetaAccountToUi(metaAccount: MetaAccount): AccountUi { val icon = walletUiUseCase.walletIcon(metaAccount) - val chainAddress = metaAccount.addressIn(destinationChainFlow.await()) + val chainAddress = metaAccount.addressIn(chainFlow.await()) val isSelected = chainAddress != null && chainAddress == selectedAddress return AccountUi( 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 d4cc391f81..aa03e7195d 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 @@ -14,6 +14,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.wallet.WalletUiUseCase import io.novafoundation.nova.feature_account_impl.R +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType class ProxyFormatter( private val walletUiUseCase: WalletUiUseCase, @@ -41,17 +42,17 @@ class ProxyFormatter( .append(proxyAccountName, colorSpan(resourceManager.getColor(R.color.text_primary))) } - fun mapProxyTypeToString(type: ProxyAccount.ProxyType): String { + fun mapProxyTypeToString(type: 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_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) - 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(separator = " ") { it.capitalize() } + ProxyType.Any -> resourceManager.getString(R.string.account_proxy_type_any) + ProxyType.NonTransfer -> resourceManager.getString(R.string.account_proxy_type_non_transfer) + ProxyType.Governance -> resourceManager.getString(R.string.account_proxy_type_governance) + ProxyType.Staking -> resourceManager.getString(R.string.account_proxy_type_staking) + ProxyType.IdentityJudgement -> resourceManager.getString(R.string.account_proxy_type_identity_judgement) + ProxyType.CancelProxy -> resourceManager.getString(R.string.account_proxy_type_cancel_proxy) + ProxyType.Auction -> resourceManager.getString(R.string.account_proxy_type_auction) + ProxyType.NominationPools -> resourceManager.getString(R.string.account_proxy_type_nomination_pools) + is ProxyType.Other -> type.name.splitCamelCase().joinToString(separator = " ") { 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/list/selectAddress/SelectAddressFragment.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/SelectAddressFragment.kt index edbacc2189..c229745fed 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/SelectAddressFragment.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/SelectAddressFragment.kt @@ -3,7 +3,7 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.list.se import android.os.Bundle import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressForTransactionRequester +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressRequester import io.novafoundation.nova.feature_account_impl.R import io.novafoundation.nova.feature_account_impl.di.AccountFeatureComponent import io.novafoundation.nova.feature_account_impl.presentation.account.list.WalletListFragment @@ -13,7 +13,7 @@ class SelectAddressFragment : WalletListFragment() { companion object { private const val KEY_REQUEST = "KEY_REQUEST" - fun getBundle(request: SelectAddressForTransactionRequester.Request): Bundle { + fun getBundle(request: SelectAddressRequester.Request): Bundle { return Bundle().apply { putParcelable(KEY_REQUEST, request) } 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 8405dcfe04..5ae194fd6d 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 @@ -3,30 +3,36 @@ package io.novafoundation.nova.feature_account_impl.presentation.account.list.se import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountInteractor 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_api.presenatation.mixin.selectAddress.SelectAddressRequester.Request +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressResponder import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountValidForTransactionListingMixinFactory import io.novafoundation.nova.feature_account_impl.presentation.account.list.WalletListViewModel +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.toMetaAccountsFilter import kotlinx.coroutines.launch class SelectAddressViewModel( accountListingMixinFactory: MetaAccountValidForTransactionListingMixinFactory, private val router: AccountRouter, - private val selectAddressResponder: SelectAddressForTransactionResponder, + private val selectAddressResponder: SelectAddressResponder, private val accountInteractor: AccountInteractor, - private val request: SelectAddressForTransactionRequester.Request, + private val request: Request, ) : WalletListViewModel() { - override val walletsListingMixin = accountListingMixinFactory.create(this, request.fromChainId, request.destinationChainId, request.selectedAddress) + override val walletsListingMixin = accountListingMixinFactory.create( + this, + request.chainId, + request.selectedAddress, + request.filter.toMetaAccountsFilter() + ) override val mode: AccountHolder.Mode = AccountHolder.Mode.SWITCH override fun accountClicked(accountModel: AccountUi) { launch { - val address = accountInteractor.getChainAddress(accountModel.id, request.destinationChainId) + val address = accountInteractor.getChainAddress(accountModel.id, request.chainId) if (address != null) { - selectAddressResponder.respond(SelectAddressForTransactionResponder.Response(address)) + selectAddressResponder.respond(SelectAddressResponder.Response(address)) router.back() } } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/di/SelectAddressComponent.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/di/SelectAddressComponent.kt index b11a033f33..9a5d5521ff 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/di/SelectAddressComponent.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/di/SelectAddressComponent.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_api.presenatation.account.wallet.list.SelectAddressForTransactionRequester +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressRequester import io.novafoundation.nova.feature_account_impl.presentation.account.list.selectAddress.SelectAddressFragment @Subcomponent( @@ -20,7 +20,7 @@ interface SelectAddressComponent { fun create( @BindsInstance fragment: Fragment, - @BindsInstance request: SelectAddressForTransactionRequester.Request + @BindsInstance request: SelectAddressRequester.Request ): SelectAddressComponent } diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/di/SelectAddressModule.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/di/SelectAddressModule.kt index 05ea40d16c..a016f1c55f 100644 --- a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/di/SelectAddressModule.kt +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/list/selectAddress/di/SelectAddressModule.kt @@ -12,8 +12,8 @@ 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.MetaAccountGroupingInteractor import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressCommunicator -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressForTransactionRequester +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressRequester import io.novafoundation.nova.feature_account_impl.presentation.AccountRouter import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountTypePresentationMapper import io.novafoundation.nova.feature_account_impl.presentation.account.common.listing.MetaAccountValidForTransactionListingMixinFactory @@ -48,7 +48,7 @@ class SelectAddressModule { router: AccountRouter, selectAddressCommunicator: SelectAddressCommunicator, accountInteractor: AccountInteractor, - request: SelectAddressForTransactionRequester.Request + request: SelectAddressRequester.Request ): ViewModel { return SelectAddressViewModel( accountListingMixinFactory = accountListingMixinFactory, diff --git a/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/mixin/SelectAddressMixin.kt b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/mixin/SelectAddressMixin.kt new file mode 100644 index 0000000000..36d8003f5c --- /dev/null +++ b/feature-account-impl/src/main/java/io/novafoundation/nova/feature_account_impl/presentation/account/mixin/SelectAddressMixin.kt @@ -0,0 +1,58 @@ +package io.novafoundation.nova.feature_account_impl.presentation.account.mixin + +import io.novafoundation.nova.feature_account_api.domain.interfaces.MetaAccountGroupingInteractor +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressRequester +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.toRequestFilter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach + +class SelectAddressMixinFactory( + private val selectAddressRequester: SelectAddressRequester, + private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, +) : SelectAddressMixin.Factory { + + override fun create( + coroutineScope: CoroutineScope, + payloadFlow: Flow, + onAddressSelect: (String) -> Unit + ): SelectAddressMixin { + return RealSelectAddressMixin( + coroutineScope, + selectAddressRequester, + payloadFlow, + metaAccountGroupingInteractor, + onAddressSelect + ) + } +} + +class RealSelectAddressMixin( + private val coroutineScope: CoroutineScope, + private val selectAddressRequester: SelectAddressRequester, + private val payloadFlow: Flow, + private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, + private val onAddressSelect: (String) -> Unit +) : SelectAddressMixin { + + init { + selectAddressRequester.responseFlow + .onEach { onAddressSelect(it.selectedAddress) } + .launchIn(coroutineScope) + } + + override val isSelectAddressAvailableFlow: Flow = payloadFlow.map { payload -> + metaAccountGroupingInteractor.hasAvailableMetaAccountsForChain(payload.chain.id, payload.filter) + } + + override suspend fun openSelectAddress(selectedAddress: String?) { + val payload = payloadFlow.first() + val metaAccountFilter = payload.filter.toRequestFilter() + val request = SelectAddressRequester.Request(payload.chain.id, selectedAddress, metaAccountFilter) + selectAddressRequester.openRequest(request) + } +} 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 d7fbc7dcf8..d2687c04b8 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 @@ -10,10 +10,10 @@ 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_proxy_api.domain.model.ProxyType 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 @@ -54,7 +54,7 @@ class RealProxySigningPresenter( override suspend fun notEnoughPermission( proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount, - proxyTypes: List + proxyTypes: List ) = withContext(Dispatchers.Main) { suspendCoroutine { continuation -> ProxySignNotEnoughPermissionBottomSheet( @@ -121,7 +121,7 @@ class RealProxySigningPresenter( private fun formatNotEnoughPermissionWarning( proxiedMetaAccount: MetaAccount, proxyMetaAccount: MetaAccount, - proxyTypes: List + proxyTypes: List ): CharSequence { val primaryColor = resourceManager.getColor(R.color.text_primary) diff --git a/feature-account-impl/src/main/res/layout/pincode_view.xml b/feature-account-impl/src/main/res/layout/pincode_view.xml index b020c4eda0..9c4161862e 100644 --- a/feature-account-impl/src/main/res/layout/pincode_view.xml +++ b/feature-account-impl/src/main/res/layout/pincode_view.xml @@ -109,7 +109,7 @@ android:id="@+id/btnDelete" style="@style/Widget.Nova.Button.PinCodeControlButton" android:layout_margin="@dimen/pin_code_view_spacing" - android:src="@drawable/ic_delete" + android:src="@drawable/ic_clear_pin_code_outline" android:tint="@color/color_delete_button" /> diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt index 76932584b2..a401b17e45 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt @@ -6,7 +6,7 @@ import io.novafoundation.nova.common.di.CommonApi import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator import io.novafoundation.nova.feature_assets.presentation.AssetsRouter import io.novafoundation.nova.feature_assets.presentation.balance.detail.di.BalanceDetailComponent import io.novafoundation.nova.feature_assets.presentation.balance.filters.di.AssetFiltersComponent diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt index b74f4230ae..89c652c385 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureDependencies.kt @@ -33,6 +33,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.W import io.novafoundation.nova.feature_account_api.presenatation.account.watchOnly.WatchOnlyMissingKeysPresenter import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin import io.novafoundation.nova.feature_buy_api.presentation.mixin.BuyMixin import io.novafoundation.nova.feature_buy_api.presentation.mixin.BuyMixinUi import io.novafoundation.nova.feature_crowdloan_api.data.repository.ContributionsRepository @@ -237,5 +238,7 @@ interface AssetsFeatureDependencies { val bottomSheetLauncher: DescriptionBottomSheetLauncher + val selectAddressMixinFactory: SelectAddressMixin.Factory + val chainStateRepository: ChainStateRepository } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureHolder.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureHolder.kt index 79e375ab78..d5cef28fc6 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureHolder.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureHolder.kt @@ -5,7 +5,7 @@ import io.novafoundation.nova.common.di.FeatureContainer import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator import io.novafoundation.nova.feature_assets.presentation.AssetsRouter import io.novafoundation.nova.feature_buy_api.di.BuyFeatureApi import io.novafoundation.nova.feature_crowdloan_api.di.CrowdloanFeatureApi diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendFragment.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendFragment.kt index 2411ad0874..506710e212 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendFragment.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/SelectSendFragment.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.os.bundleOf -import androidx.core.view.isInvisible import io.novafoundation.nova.common.base.BaseFragment import io.novafoundation.nova.common.di.FeatureUtils import io.novafoundation.nova.common.mixin.impl.observeValidations @@ -16,6 +15,7 @@ import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInp import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.removeInputKeyboardCallback import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.setupAddressInput import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.setupExternalAccounts +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.setupYourWalletsBtn import io.novafoundation.nova.feature_assets.R import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi import io.novafoundation.nova.feature_assets.di.AssetsFeatureComponent @@ -64,7 +64,6 @@ class SelectSendFragment : BaseFragment() { selectSendOriginChain.setOnClickListener { viewModel.originChainClicked() } selectSendDestinationChain.setOnClickListener { viewModel.destinationChainClicked() } - selectWallet.background = getRoundedCornerDrawable(cornerSizeDp = 8).withRippleMask(getRippleMask(cornerSizeDp = 8)) selectWallet.setOnClickListener { viewModel.selectRecipientWallet() } selectSendCrossChainFee.makeGone() // gone inititally @@ -92,6 +91,7 @@ class SelectSendFragment : BaseFragment() { setupAmountChooser(viewModel.amountChooserMixin, selectSendAmount) setupAddressInput(viewModel.addressInputMixin, selectSendRecipient) setupExternalAccounts(viewModel.addressInputMixin, selectSendRecipient) + setupYourWalletsBtn(selectWallet, viewModel.selectAddressMixin) viewModel.chooseDestinationChain.awaitableActionLiveData.observeEvent { removeInputKeyboardCallback(selectSendRecipient) @@ -105,10 +105,6 @@ class SelectSendFragment : BaseFragment() { crossChainDestinationBottomSheet.show() } - viewModel.isSelectAddressAvailable.observe { - selectWallet.isInvisible = !it - } - viewModel.transferDirectionModel.observe { selectSendOriginChain.setModel(it.originChip) selectSendFromTitle.text = it.originChainLabel 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 f50d16cbde..119975a614 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 @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_assets.presentation.send.amount import androidx.lifecycle.viewModelScope +import io.novafoundation.nova.common.address.intoKey import io.novafoundation.nova.common.base.BaseViewModel import io.novafoundation.nova.common.list.headers.TextHeader import io.novafoundation.nova.common.mixin.actionAwaitable.ActionAwaitableMixin @@ -13,11 +14,15 @@ import io.novafoundation.nova.common.validation.ValidationExecutor import io.novafoundation.nova.common.validation.progressConsumer import io.novafoundation.nova.common.view.ButtonState import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi +import io.novafoundation.nova.feature_account_api.domain.filter.selectAddress.SelectAddressAccountFilter +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.interfaces.SelectedAccountUseCase -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressForTransactionRequester +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.actions.ExternalActions import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin import io.novafoundation.nova.feature_account_api.view.ChainChipModel import io.novafoundation.nova.feature_assets.R import io.novafoundation.nova.feature_assets.domain.WalletInteractor @@ -75,14 +80,15 @@ class SelectSendViewModel( private val initialRecipientAddress: String?, private val validationExecutor: ValidationExecutor, private val resourceManager: ResourceManager, - private val selectAddressRequester: SelectAddressForTransactionRequester, private val externalActions: ExternalActions.Presentation, private val crossChainTransfersUseCase: CrossChainTransfersUseCase, + private val accountRepository: AccountRepository, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, feeLoaderMixinFactory: FeeLoaderMixin.Factory, selectedAccountUseCase: SelectedAccountUseCase, addressInputMixinFactory: AddressInputMixinFactory, amountChooserMixinFactory: AmountChooserMixin.Factory, + selectAddressMixinFactory: SelectAddressMixin.Factory ) : BaseViewModel(), Validatable by validationExecutor, ExternalActions by externalActions { @@ -96,6 +102,18 @@ class SelectSendViewModel( private val destinationAsset = destinationChainWithAsset.map { it.asset } private val destinationChain = destinationChainWithAsset.map { it.chain } + private val selectAddressPayloadFlow = combine( + originChain, + destinationChain + ) { origin, destination -> + SelectAddressMixin.Payload( + chain = destination, + filter = getMetaAccountsFilter(origin, destination) + ) + } + + val selectAddressMixin = selectAddressMixinFactory.create(this, selectAddressPayloadFlow, ::onAddressSelect) + val addressInputMixin = with(addressInputMixinFactory) { val destinationChain = destinationChainWithAsset.map { it.chain } val inputSpec = singleChainInputSpec(destinationChain) @@ -118,11 +136,6 @@ class SelectSendViewModel( .onStart { emit(emptyList()) } .shareInBackground() - val isSelectAddressAvailable = combine(originChain, destinationChain) { originChain, destinationChain -> - metaAccountGroupingInteractor.hasAvailableMetaAccountsForDestination(originChain.id, destinationChain.id) - } - .shareInBackground() - val transferDirectionModel = combine( availableCrossChainDestinations, originChainWithAsset, @@ -168,8 +181,6 @@ class SelectSendViewModel( init { subscribeOnChangeDestination() - subscribeOnSelectAddress() - setInitialState() setupFees() @@ -240,10 +251,7 @@ class SelectSendViewModel( fun selectRecipientWallet() { launch { val selectedAddress = addressInputMixin.inputFlow.value - val currentOriginChain = originChain.first() - val currentDestinationChain = destinationChain.first() - val request = SelectAddressForTransactionRequester.Request(currentOriginChain.id, currentDestinationChain.id, selectedAddress) - selectAddressRequester.openRequest(request) + selectAddressMixin.openSelectAddress(selectedAddress) } } @@ -260,12 +268,8 @@ class SelectSendViewModel( .launchIn(this) } - private fun subscribeOnSelectAddress() { - selectAddressRequester.responseFlow - .onEach { - addressInputMixin.inputFlow.value = it.selectedAddress - } - .launchIn(this) + private fun onAddressSelect(address: String) { + addressInputMixin.inputFlow.value = address } private fun setInitialState() = launch { @@ -491,6 +495,23 @@ class SelectSendViewModel( } } + private suspend fun getMetaAccountsFilter(origin: Chain, desination: Chain): SelectAddressAccountFilter { + val isCrossChain = origin.id != desination.id + + return if (isCrossChain) { + SelectAddressAccountFilter.Everything() + } else { + val destinationAccountId = selectedAccount.first().requireAccountIdIn(desination) + val notOriginMetaAccounts = accountRepository.activeMetaAccounts() + .filter { it.accountIdIn(origin)?.intoKey() == destinationAccountId.intoKey() } + .map { it.id } + + SelectAddressAccountFilter.ExcludeMetaAccounts( + notOriginMetaAccounts + ) + } + } + private class CrossChainDirection( val chainWithAsset: ChainWithAsset, val balances: Asset? diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/di/SelectSendModule.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/di/SelectSendModule.kt index 062ee11bb1..6f5418c41d 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/di/SelectSendModule.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/send/amount/di/SelectSendModule.kt @@ -11,11 +11,12 @@ 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.validation.ValidationExecutor +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.interfaces.SelectedAccountUseCase -import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.list.SelectAddressCommunicator import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin import io.novafoundation.nova.feature_assets.domain.WalletInteractor import io.novafoundation.nova.feature_assets.domain.send.SendInteractor import io.novafoundation.nova.feature_assets.presentation.AssetsRouter @@ -42,7 +43,6 @@ class SelectSendModule { initialRecipientAddress: String?, validationExecutor: ValidationExecutor, resourceManager: ResourceManager, - selectAddressRequester: SelectAddressCommunicator, externalActions: ExternalActions.Presentation, crossChainTransfersUseCase: CrossChainTransfersUseCase, actionAwaitableMixinFactory: ActionAwaitableMixin.Factory, @@ -50,6 +50,8 @@ class SelectSendModule { selectedAccountUseCase: SelectedAccountUseCase, addressInputMixinFactory: AddressInputMixinFactory, amountChooserMixinFactory: AmountChooserMixin.Factory, + accountRepository: AccountRepository, + selectAddressMixinFactory: SelectAddressMixin.Factory ): ViewModel { return SelectSendViewModel( chainRegistry = chainRegistry, @@ -61,14 +63,15 @@ class SelectSendModule { initialRecipientAddress = initialRecipientAddress, validationExecutor = validationExecutor, resourceManager = resourceManager, - selectAddressRequester = selectAddressRequester, externalActions = externalActions, crossChainTransfersUseCase = crossChainTransfersUseCase, actionAwaitableMixinFactory = actionAwaitableMixinFactory, feeLoaderMixinFactory = feeLoaderMixinFactory, selectedAccountUseCase = selectedAccountUseCase, addressInputMixinFactory = addressInputMixinFactory, - amountChooserMixinFactory = amountChooserMixinFactory + amountChooserMixinFactory = amountChooserMixinFactory, + accountRepository = accountRepository, + selectAddressMixinFactory = selectAddressMixinFactory ) } diff --git a/feature-assets/src/main/res/layout/fragment_select_send.xml b/feature-assets/src/main/res/layout/fragment_select_send.xml index 9cad5f1582..b64d19866e 100644 --- a/feature-assets/src/main/res/layout/fragment_select_send.xml +++ b/feature-assets/src/main/res/layout/fragment_select_send.xml @@ -69,7 +69,7 @@ android:clipChildren="false" android:minHeight="24dp" android:paddingStart="16dp" - android:paddingEnd="12dp"> + android:paddingEnd="16dp"> - - - - - - - - - + android:layout_gravity="end|center_vertical"/> @@ -129,7 +94,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:layout_marginTop="4dp" + android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:hint="@string/send_address_hint" app:hasExternalAccountIdentifiers="true" /> 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 ee76c93548..acd7086dee 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 @@ -23,7 +23,7 @@ fun ValidationSystem.Companion.chooseDelegationAmount( error = { context -> ChooseDelegationAmountValidationFailure.NotEnoughToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 076c4245dc..7b94f54a97 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 @@ -12,7 +12,7 @@ fun ValidationSystem.Companion.removeVotesValidationSystem(): RemoteVotesValidat error = { context -> RemoveVotesValidationFailure.NotEnoughToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 285a4d789b..9f8ea83016 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 @@ -12,7 +12,7 @@ fun ValidationSystem.Companion.revokeDelegationValidationSystem(): RevokeDelegat error = { context -> RevokeDelegationValidationFailure.NotEnoughToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 10dc1400fa..2eeb2ae956 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 @@ -12,7 +12,7 @@ fun ValidationSystem.Companion.unlockReferendumValidationSystem(): UnlockReferen error = { context -> UnlockGovernanceValidationFailure.NotEnoughToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 f33fd17562..a5da04c2c5 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 @@ -29,7 +29,7 @@ fun ValidationSystem.Companion.voteReferendumValidationSystem( error = { context -> VoteReferendumValidationFailure.NotEnoughToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 7eddf9c460..7df9e9b46d 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 @@ -104,7 +104,7 @@ class DelegateDetailsViewModel( when { data == null -> DescriptiveButtonState.Gone data.userDelegations.isNotEmpty() -> DescriptiveButtonState.Gone - else -> DescriptiveButtonState.Enabled(resourceManager.getString(R.string.delegation_add_delegation)) + else -> DescriptiveButtonState.Enabled(resourceManager.getString(R.string.common_add_delegation)) } }.shareWhileSubscribed() diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/common/NewDelegationTitle.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/common/NewDelegationTitle.kt index 82ca55df2f..1e0df7098d 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/common/NewDelegationTitle.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/delegation/delegation/create/common/NewDelegationTitle.kt @@ -4,7 +4,7 @@ import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_governance_impl.R internal fun ResourceManager.newDelegationTitle(isEditMode: Boolean): String { - val resId = if (isEditMode) R.string.delegation_edit_delegation else R.string.delegation_add_delegation + val resId = if (isEditMode) R.string.delegation_edit_delegation else R.string.common_add_delegation return getString(resId) } diff --git a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt index 37a4393221..1255499bf7 100644 --- a/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt +++ b/feature-governance-impl/src/main/java/io/novafoundation/nova/feature_governance_impl/presentation/referenda/list/ReferendaListViewModel.kt @@ -143,7 +143,7 @@ class ReferendaListViewModel( DelegatedState.NotDelegated -> GovernanceLocksModel( amount = null, - title = resourceManager.getString(R.string.delegation_add_delegation), + title = resourceManager.getString(R.string.common_add_delegation), hasUnlockableLocks = false ) diff --git a/feature-governance-impl/src/main/res/layout/fragment_delegate_details.xml b/feature-governance-impl/src/main/res/layout/fragment_delegate_details.xml index 941a7c611f..f3dd22d019 100644 --- a/feature-governance-impl/src/main/res/layout/fragment_delegate_details.xml +++ b/feature-governance-impl/src/main/res/layout/fragment_delegate_details.xml @@ -174,7 +174,7 @@ android:layout_gravity="bottom" android:layout_marginHorizontal="16dp" android:layout_marginBottom="16dp" - android:text="@string/delegation_add_delegation" /> + android:text="@string/common_add_delegation" /> + app:titleText="@string/common_add_delegation" /> + app:titleText="@string/common_add_delegation" /> + app:titleText="@string/common_add_delegation" /> + app:titleIconStartTint="@color/icon_secondary" /> diff --git a/feature-governance-impl/src/main/res/layout/fragment_your_delegations.xml b/feature-governance-impl/src/main/res/layout/fragment_your_delegations.xml index 18a5dc4d17..064b7df2d8 100644 --- a/feature-governance-impl/src/main/res/layout/fragment_your_delegations.xml +++ b/feature-governance-impl/src/main/res/layout/fragment_your_delegations.xml @@ -37,7 +37,7 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="16dp" android:layout_marginBottom="24dp" - android:text="@string/delegation_add_delegation" + android:text="@string/common_add_delegation" app:layout_constraintBottom_toBottomOf="parent" app:size="large" /> diff --git a/feature-governance-impl/src/main/res/layout/item_referenda_header.xml b/feature-governance-impl/src/main/res/layout/item_referenda_header.xml index 86c5425d44..a71e3b3a7f 100644 --- a/feature-governance-impl/src/main/res/layout/item_referenda_header.xml +++ b/feature-governance-impl/src/main/res/layout/item_referenda_header.xml @@ -44,7 +44,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" app:governanceLocksView_icon="@drawable/ic_delegate_outline" - app:governanceLocksView_label="@string/delegation_add_delegation" /> + app:governanceLocksView_label="@string/common_add_delegation" /> diff --git a/feature-proxy-api/.gitignore b/feature-proxy-api/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/feature-proxy-api/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature-proxy-api/build.gradle b/feature-proxy-api/build.gradle new file mode 100644 index 0000000000..8844dda1bf --- /dev/null +++ b/feature-proxy-api/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion rootProject.compileSdkVersion + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + kotlinOptions { + jvmTarget = '1.8' + freeCompilerArgs = ["-Xcontext-receivers"] + } +} + +dependencies { + implementation project(":common") + implementation project(':runtime') + + implementation fearlessLibDep +} \ No newline at end of file diff --git a/feature-proxy-api/consumer-rules.pro b/feature-proxy-api/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/feature-proxy-api/proguard-rules.pro b/feature-proxy-api/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/feature-proxy-api/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature-proxy-api/src/main/AndroidManifest.xml b/feature-proxy-api/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..10c74f4bde --- /dev/null +++ b/feature-proxy-api/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/calls/ExtrinsicBuilderExt.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/calls/ExtrinsicBuilderExt.kt new file mode 100644 index 0000000000..e483f3fa1b --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/calls/ExtrinsicBuilderExt.kt @@ -0,0 +1,34 @@ +package io.novafoundation.nova.feature_proxy_api.data.calls + +import io.novafoundation.nova.common.utils.Modules +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import java.math.BigInteger +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.instances.AddressInstanceConstructor +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.ExtrinsicBuilder + +fun ExtrinsicBuilder.addProxyCall(proxyAccountId: AccountId, proxyType: ProxyType): ExtrinsicBuilder { + return call( + Modules.PROXY, + "add_proxy", + argumentsForProxy(runtime, proxyAccountId, proxyType) + ) +} + +fun ExtrinsicBuilder.removeProxyCall(proxyAccountId: AccountId, proxyType: ProxyType): ExtrinsicBuilder { + return call( + Modules.PROXY, + "remove_proxy", + argumentsForProxy(runtime, proxyAccountId, proxyType) + ) +} + +private fun argumentsForProxy(runtime: RuntimeSnapshot, proxyAccountId: AccountId, proxyType: ProxyType): Map { + return mapOf( + "delegate" to AddressInstanceConstructor.constructInstance(runtime.typeRegistry, proxyAccountId), + "proxy_type" to DictEnum.Entry(proxyType.name, null), + "delay" to BigInteger.ZERO + ) +} diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/DepositBaseAndFactor.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/DepositBaseAndFactor.kt new file mode 100644 index 0000000000..55442f8855 --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/DepositBaseAndFactor.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.feature_proxy_api.data.common + +import java.math.BigInteger + +class DepositBaseAndFactor( + val baseAmount: BigInteger, + val factorAmount: BigInteger +) diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/NestedProxiesGraphConstructor.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/NestedProxiesGraphConstructor.kt new file mode 100644 index 0000000000..f38de14780 --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/NestedProxiesGraphConstructor.kt @@ -0,0 +1,50 @@ +package io.novafoundation.nova.feature_proxy_api.data.common + +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.common.utils.mapToSet +import io.novafoundation.nova.feature_proxy_api.data.common.NestedProxiesGraphConstructor.Node +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.feature_proxy_api.domain.model.min +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +interface NestedProxiesGraphConstructor { + + fun build(): List + + data class Node( + val accountId: AccountIdKey, + val permissionType: ProxyType, + var nestedNodes: List, + val path: Map + ) { + fun hasInPath(otherAccountId: AccountIdKey): Boolean { + return path.contains(otherAccountId) || accountId == otherAccountId + } + + fun setNested(nodes: List) { + nestedNodes = nodes + } + + fun flatten(): List { + val result = mutableListOf() + result.add(this) + result.addAll(nestedNodes.flatMap { it.flatten() }) + return result + } + + companion object + } +} + +fun Node.Companion.getAllAccountIds(nodes: List): Set { + return flatten(nodes).mapToSet { it.accountId.value } +} + +fun Node.Companion.flatten(nodes: List): List { + return nodes.flatMap { it.flatten() } +} + +fun ProxyType.isMutuallyExclusiveWith(node: Node): Boolean { + val fullPermissionPath = node.path.values + node.permissionType + return fullPermissionPath.any { ProxyType.min(it, this) == null } +} diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/ProxyDepositCalculator.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/ProxyDepositCalculator.kt new file mode 100644 index 0000000000..736e184496 --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/common/ProxyDepositCalculator.kt @@ -0,0 +1,11 @@ +package io.novafoundation.nova.feature_proxy_api.data.common + +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import java.math.BigInteger + +interface ProxyDepositCalculator { + + fun calculateProxyDepositForQuantity(baseAndFactor: DepositBaseAndFactor, proxiesCount: Int): BigInteger + + suspend fun calculateProxyDepositForQuantity(chainId: ChainId, proxiesCount: Int): BigInteger +} diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/model/ProxyPermission.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/model/ProxyPermission.kt new file mode 100644 index 0000000000..87d8633c74 --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/model/ProxyPermission.kt @@ -0,0 +1,10 @@ +package io.novafoundation.nova.feature_proxy_api.data.model + +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType + +data class ProxyPermission( + val proxiedAccountId: AccountIdKey, + val proxyAccountId: AccountIdKey, + val proxyType: ProxyType +) diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/repository/GetProxyRepository.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/repository/GetProxyRepository.kt new file mode 100644 index 0000000000..6858446a8a --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/repository/GetProxyRepository.kt @@ -0,0 +1,30 @@ +package io.novafoundation.nova.feature_proxy_api.data.repository + +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.feature_proxy_api.data.common.NestedProxiesGraphConstructor +import io.novafoundation.nova.feature_proxy_api.data.model.ProxyPermission +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import java.math.BigInteger +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.flow.Flow + +interface GetProxyRepository { + + suspend fun findAllProxiedsForAccounts(chainId: ChainId, accountIds: Set): List + + suspend fun getDelegatedProxyTypesRemote(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List + + suspend fun getDelegatedProxyTypesLocal(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List + + suspend fun getProxiesQuantity(chainId: ChainId, proxiedAccountId: AccountId): Int + + suspend fun getProxyDeposit(chainId: ChainId, proxiedAccountId: AccountId): BigInteger + + suspend fun maxProxiesQuantity(chain: Chain): Int + + fun proxiesByTypeFlow(chain: Chain, accountId: AccountId, proxyType: ProxyType): Flow> + + fun proxiesQuantityByTypeFlow(chain: Chain, accountId: AccountId, proxyType: ProxyType): Flow +} diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/repository/ProxyConstantsRepository.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/repository/ProxyConstantsRepository.kt new file mode 100644 index 0000000000..010519ac0d --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/data/repository/ProxyConstantsRepository.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.feature_proxy_api.data.repository + +import io.novafoundation.nova.feature_proxy_api.data.common.DepositBaseAndFactor +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId + +interface ProxyConstantsRepository { + + suspend fun getDepositConstants(chainId: ChainId): DepositBaseAndFactor +} diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/di/ProxyFeatureApi.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/di/ProxyFeatureApi.kt new file mode 100644 index 0000000000..cebeb79969 --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/di/ProxyFeatureApi.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_proxy_api.di + +import io.novafoundation.nova.feature_proxy_api.data.common.ProxyDepositCalculator +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository + +interface ProxyFeatureApi { + + val proxyRepository: GetProxyRepository + + val proxyDepositCalculator: ProxyDepositCalculator + + val proxyConstantsRepository: ProxyConstantsRepository +} diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/model/ProxyType.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/model/ProxyType.kt new file mode 100644 index 0000000000..3711fddb37 --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/model/ProxyType.kt @@ -0,0 +1,47 @@ +package io.novafoundation.nova.feature_proxy_api.domain.model + +sealed class ProxyType(val name: String, val controllableFrom: List) { + + object Any : ProxyType("Any", emptyList()) + + object NonTransfer : ProxyType("NonTransfer", listOf(Any)) + + object Governance : ProxyType("Governance", listOf(Any, NonTransfer)) + + object Staking : ProxyType("Staking", listOf(Any, NonTransfer)) + + object IdentityJudgement : ProxyType("IdentityJudgement", listOf(Any, NonTransfer)) + + object CancelProxy : ProxyType("CancelProxy", listOf(Any, NonTransfer)) + + object Auction : ProxyType("Auction", listOf(Any, NonTransfer)) + + object NominationPools : ProxyType("NominationPools", listOf(Any, NonTransfer, Staking)) + + class Other(name: String) : ProxyType(name, listOf(Any)) + + companion object +} + +fun ProxyType.Companion.fromString(name: String): ProxyType { + return when (name) { + "Any" -> ProxyType.Any + "NonTransfer" -> ProxyType.NonTransfer + "Governance" -> ProxyType.Governance + "Staking" -> ProxyType.Staking + "IdentityJudgement" -> ProxyType.IdentityJudgement + "CancelProxy" -> ProxyType.CancelProxy + "Auction" -> ProxyType.Auction + "NominationPools" -> ProxyType.NominationPools + else -> ProxyType.Other(name) + } +} + +fun ProxyType.isControllableFrom(proxyType: ProxyType): Boolean { + return name == proxyType.name || controllableFrom.contains(proxyType) +} + +fun ProxyType.Companion.min(first: ProxyType, second: ProxyType): ProxyType? { + return first.takeIf { it.isControllableFrom(second) } + ?: second.takeIf { it.isControllableFrom(first) } +} diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/validators/MaximumProxiesNotReachedValidation.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/validators/MaximumProxiesNotReachedValidation.kt new file mode 100644 index 0000000000..9ab2c8f7ba --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/validators/MaximumProxiesNotReachedValidation.kt @@ -0,0 +1,45 @@ +package io.novafoundation.nova.feature_proxy_api.domain.validators + +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_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class MaximumProxiesNotReachedValidation( + private val chain: (P) -> Chain, + private val accountId: (P) -> AccountId, + private val proxiesQuantity: (P) -> Int, + private val error: (P, Int) -> E, + private val proxyRepository: GetProxyRepository +) : Validation { + + override suspend fun validate(value: P): ValidationStatus { + val newProxiesQuantity = proxiesQuantity(value) + val maximumProxiesQuantiy = proxyRepository.maxProxiesQuantity(chain(value)) + + return validOrError(newProxiesQuantity <= maximumProxiesQuantiy) { + error(value, maximumProxiesQuantiy) + } + } +} + +fun ValidationSystemBuilder.maximumProxiesNotReached( + chain: (P) -> Chain, + accountId: (P) -> AccountId, + proxiesQuantity: (P) -> Int, + error: (P, Int) -> E, + proxyRepository: GetProxyRepository +) { + validate( + MaximumProxiesNotReachedValidation( + chain = chain, + accountId = accountId, + proxiesQuantity = proxiesQuantity, + error = error, + proxyRepository = proxyRepository + ) + ) +} diff --git a/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/validators/ProxyIsNotDuplicationForAccount.kt b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/validators/ProxyIsNotDuplicationForAccount.kt new file mode 100644 index 0000000000..fbf78225a8 --- /dev/null +++ b/feature-proxy-api/src/main/java/io/novafoundation/nova/feature_proxy_api/domain/validators/ProxyIsNotDuplicationForAccount.kt @@ -0,0 +1,49 @@ +package io.novafoundation.nova.feature_proxy_api.domain.validators + +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_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class ProxyIsNotDuplicationForAccount( + private val chain: (P) -> Chain, + private val proxiedAccountId: (P) -> AccountId, + private val proxyAccountId: (P) -> AccountId, + private val proxyType: (P) -> ProxyType, + private val error: (P) -> E, + private val proxyRepository: GetProxyRepository +) : Validation { + + override suspend fun validate(value: P): ValidationStatus { + val chain = chain(value) + val proxyTypes = proxyRepository.getDelegatedProxyTypesLocal(chain.id, proxiedAccountId(value), proxyAccountId(value)) + + return validOrError(!proxyTypes.contains(proxyType(value))) { + error(value) + } + } +} + +fun ValidationSystemBuilder.proxyIsNotDuplicationForAccount( + chain: (P) -> Chain, + proxiedAccountId: (P) -> AccountId, + proxyAccountId: (P) -> AccountId, + proxyType: (P) -> ProxyType, + error: (P) -> E, + proxyRepository: GetProxyRepository +) { + validate( + ProxyIsNotDuplicationForAccount( + chain = chain, + proxiedAccountId = proxiedAccountId, + proxyAccountId = proxyAccountId, + proxyType = proxyType, + error = error, + proxyRepository = proxyRepository + ) + ) +} diff --git a/feature-proxy-impl/.gitignore b/feature-proxy-impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/feature-proxy-impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature-proxy-impl/build.gradle b/feature-proxy-impl/build.gradle new file mode 100644 index 0000000000..c2024b6284 --- /dev/null +++ b/feature-proxy-impl/build.gradle @@ -0,0 +1,66 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' +apply from: '../tests.gradle' +apply from: '../scripts/secrets.gradle' + +android { + compileSdkVersion rootProject.compileSdkVersion + namespace 'io.novafoundation.nova.feature_proxy' + + defaultConfig { + minSdkVersion rootProject.minSdkVersion + targetSdkVersion rootProject.targetSdkVersion + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + + kotlinOptions { + jvmTarget = '1.8' + freeCompilerArgs = ["-Xcontext-receivers"] + } +} + +dependencies { + implementation project(':common') + implementation project(':runtime') + implementation project(':feature-proxy-api') + + implementation kotlinDep + + implementation androidDep + implementation materialDep + implementation constraintDep + + implementation coroutinesDep + implementation coroutinesAndroidDep + implementation viewModelKtxDep + implementation lifeCycleKtxDep + + implementation daggerDep + kapt daggerKapt + + testImplementation jUnitDep + testImplementation mockitoDep + + implementation insetterDep + + implementation shimmerDep + + androidTestImplementation androidTestRunnerDep + androidTestImplementation androidTestRulesDep + androidTestImplementation androidJunitDep +} \ No newline at end of file diff --git a/feature-proxy-impl/consumer-rules.pro b/feature-proxy-impl/consumer-rules.pro new file mode 100644 index 0000000000..e69de29bb2 diff --git a/feature-proxy-impl/proguard-rules.pro b/feature-proxy-impl/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/feature-proxy-impl/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/feature-proxy-impl/src/main/AndroidManifest.xml b/feature-proxy-impl/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..10728cc703 --- /dev/null +++ b/feature-proxy-impl/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/common/RealNestedProxiesGraphConstructor.kt b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/common/RealNestedProxiesGraphConstructor.kt new file mode 100644 index 0000000000..1c19350484 --- /dev/null +++ b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/common/RealNestedProxiesGraphConstructor.kt @@ -0,0 +1,61 @@ +package io.novafoundation.nova.feature_proxy_impl.data.common + +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.feature_proxy_api.data.common.NestedProxiesGraphConstructor +import io.novafoundation.nova.feature_proxy_api.data.common.NestedProxiesGraphConstructor.Node +import io.novafoundation.nova.feature_proxy_api.data.common.isMutuallyExclusiveWith +import io.novafoundation.nova.feature_proxy_api.data.model.ProxyPermission +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType + +class RealNestedProxiesGraphConstructor( + val startAccountIds: Set, + permissions: List +) : NestedProxiesGraphConstructor { + + val proxyToProxieds: Map> = permissions + .groupBy { it.proxyAccountId } + .mapValues { + it.value.map { + Node( + it.proxiedAccountId, + it.proxyType, + mutableListOf(), + mapOf() + ) + } + } + + override fun build(): List { + val startNodes = startAccountIds.map { Node(it, ProxyType.Any, mutableListOf(), emptyMap()) } + + fillNodes(startNodes) + + return startNodes + } + + fun fillNodes(nodes: List) { + for (node in nodes) { + val onChainNestedNodes = proxyToProxieds[node.accountId] ?: continue + + val resultNestedNodes = mutableListOf() + + for (proxiedNode in onChainNestedNodes) { + // If we have an account in full node path we skip it to avoid cycles + if (node.hasInPath(proxiedNode.accountId)) continue + + // Check that proxy type is not matually exclusive by full path + val matuallyExclusive = proxiedNode.permissionType.isMutuallyExclusiveWith(node) + if (matuallyExclusive) continue + + val nestedNodePath = node.path + mapOf(node.accountId to node.permissionType) + val nestedNode = proxiedNode.copy(path = nestedNodePath) + resultNestedNodes.add(nestedNode) + } + + if (resultNestedNodes.isNotEmpty()) { + node.setNested(resultNestedNodes) + fillNodes(node.nestedNodes) + } + } + } +} diff --git a/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/common/RealProxyDepositCalculator.kt b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/common/RealProxyDepositCalculator.kt new file mode 100644 index 0000000000..9b81e15f2b --- /dev/null +++ b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/common/RealProxyDepositCalculator.kt @@ -0,0 +1,28 @@ +package io.novafoundation.nova.feature_proxy_impl.data.common + +import io.novafoundation.nova.feature_proxy_api.data.common.DepositBaseAndFactor +import io.novafoundation.nova.feature_proxy_api.data.common.ProxyDepositCalculator +import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import java.math.BigInteger + +class RealProxyDepositCalculator( + private val chainRegestry: ChainRegistry, + private val proxyConstantsRepository: ProxyConstantsRepository +) : ProxyDepositCalculator { + + override fun calculateProxyDepositForQuantity(baseAndFactor: DepositBaseAndFactor, proxiesCount: Int): BigInteger { + return if (proxiesCount == 0) { + BigInteger.ZERO + } else { + baseAndFactor.baseAmount + baseAndFactor.factorAmount * proxiesCount.toBigInteger() + } + } + + override suspend fun calculateProxyDepositForQuantity(chainId: ChainId, proxiesCount: Int): BigInteger { + val depositAndFactor = proxyConstantsRepository.getDepositConstants(chainId) + + return calculateProxyDepositForQuantity(depositAndFactor, proxiesCount) + } +} diff --git a/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/repository/RealGetProxyRepository.kt b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/repository/RealGetProxyRepository.kt new file mode 100644 index 0000000000..99a89d6b48 --- /dev/null +++ b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/repository/RealGetProxyRepository.kt @@ -0,0 +1,173 @@ +package io.novafoundation.nova.feature_proxy_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.cast +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.common.utils.numberConstant +import io.novafoundation.nova.common.utils.proxy +import io.novafoundation.nova.feature_proxy_api.data.common.NestedProxiesGraphConstructor +import io.novafoundation.nova.feature_proxy_api.data.model.ProxyPermission +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.feature_proxy_api.domain.model.fromString +import io.novafoundation.nova.feature_proxy_impl.data.common.RealNestedProxiesGraphConstructor +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.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 kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +private class OnChainProxiedModel( + val proxies: List, + val deposit: BigInteger +) + +private class OnChainProxyModel( + val accountId: AccountIdKey, + val proxyType: String, + val delay: BigInteger +) + +class RealGetProxyRepository( + private val remoteSource: StorageDataSource, + private val localSource: StorageDataSource, + private val chainRegistry: ChainRegistry, +) : GetProxyRepository { + + override suspend fun findAllProxiedsForAccounts(chainId: ChainId, accountIds: Set): List { + val delegatorToProxies = receiveAllProxiesInChain(chainId) + val proxyPermissions = delegatorToProxies + .flatMap { (delegator, proxied) -> + val notDelayedProxies = proxied.proxies.filter { it.delay == BigInteger.ZERO } + + notDelayedProxies.map { proxy -> + ProxyPermission( + proxiedAccountId = delegator, + proxyAccountId = proxy.accountId, + proxyType = ProxyType.fromString(proxy.proxyType) + ) + } + } + + return RealNestedProxiesGraphConstructor(accountIds, proxyPermissions) + .build() + } + + override suspend fun getDelegatedProxyTypesRemote(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List { + return getDelegatedProxyTypes(remoteSource, chainId, proxiedAccountId, proxyAccountId) + } + + // TODO: use it for staking after merge "add staking proxy" branch + override suspend fun getDelegatedProxyTypesLocal(chainId: ChainId, proxiedAccountId: AccountId, proxyAccountId: AccountId): List { + return getDelegatedProxyTypes(localSource, chainId, proxiedAccountId, proxyAccountId) + } + + override suspend fun getProxiesQuantity(chainId: ChainId, proxiedAccountId: AccountId): Int { + val proxied = getAllProxiesFor(localSource, chainId, proxiedAccountId) + + return proxied.proxies.size + } + + override suspend fun getProxyDeposit(chainId: ChainId, proxiedAccountId: AccountId): BigInteger { + val proxied = getAllProxiesFor(localSource, chainId, proxiedAccountId) + + return proxied.deposit + } + + override suspend fun maxProxiesQuantity(chain: Chain): Int { + val runtime = chainRegistry.getRuntime(chain.id) + val constantQuery = runtime.metadata.proxy() + return constantQuery.numberConstant("MaxProxies", runtime).toInt() + } + + override fun proxiesByTypeFlow(chain: Chain, accountId: AccountId, proxyType: ProxyType): Flow> { + return localSource.subscribe(chain.id) { + runtime.metadata.module(Modules.PROXY) + .storage("Proxies") + .observe( + accountId, + binding = { bindProxyAccounts(it) } + ) + }.map { proxied -> + proxied.proxies + .filter { it.proxyType == proxyType.name } + .map { ProxyPermission(accountId.intoKey(), it.accountId, ProxyType.fromString(it.proxyType)) } + } + } + + override fun proxiesQuantityByTypeFlow(chain: Chain, accountId: AccountId, proxyType: ProxyType): Flow { + return proxiesByTypeFlow(chain, accountId, proxyType) + .map { it.size } + } + + private suspend fun getDelegatedProxyTypes( + storageDataSource: StorageDataSource, + chainId: ChainId, + proxiedAccountId: AccountId, + proxyAccountId: AccountId + ): List { + val proxied = getAllProxiesFor(storageDataSource, chainId, proxiedAccountId) + + return proxied.proxies + .filter { it.accountId == proxyAccountId.intoKey() } + .map { ProxyType.fromString(it.proxyType) } + } + + private suspend fun getAllProxiesFor(storageDataSource: StorageDataSource, chainId: ChainId, accountId: AccountId): OnChainProxiedModel { + return storageDataSource.query(chainId) { + runtime.metadata.module(Modules.PROXY) + .storage("Proxies") + .query( + keyArguments = arrayOf(accountId), + binding = { result -> bindProxyAccounts(result) } + ) + } + } + + private suspend fun receiveAllProxiesInChain(chainId: ChainId): Map { + return remoteSource.query(chainId) { + runtime.metadata.module(Modules.PROXY) + .storage("Proxies") + .entries( + keyExtractor = { (accountId: AccountId) -> AccountIdKey(accountId) }, + binding = { result, _ -> bindProxyAccounts(result) }, + recover = { _, _ -> + // Do nothing if entry binding throws an exception + } + ) + } + } + + private fun bindProxyAccounts(dynamicInstance: Any?): OnChainProxiedModel { + if (dynamicInstance == null) return OnChainProxiedModel(emptyList(), BigInteger.ZERO) + + val root = dynamicInstance.castToList() + val proxies = root[0].castToList() + + return OnChainProxiedModel( + proxies = proxies.map { + val proxy = it.castToStruct() + val proxyAccountId: ByteArray = proxy.getTyped("delegate") + val proxyType = proxy.get("proxyType").castToDictEnum() + val delay = proxy.getTyped("delay") + OnChainProxyModel( + proxyAccountId.intoKey(), + proxyType.name, + delay + ) + }, + deposit = root[1].cast() + ) + } +} diff --git a/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/repository/RealProxyConstantsRepository.kt b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/repository/RealProxyConstantsRepository.kt new file mode 100644 index 0000000000..c0209740d6 --- /dev/null +++ b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/data/repository/RealProxyConstantsRepository.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.feature_proxy_impl.data.repository + +import io.novafoundation.nova.common.utils.numberConstant +import io.novafoundation.nova.common.utils.proxy +import io.novafoundation.nova.feature_proxy_api.data.common.DepositBaseAndFactor +import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId +import io.novafoundation.nova.runtime.multiNetwork.getRuntime + +class RealProxyConstantsRepository( + private val chainRegestry: ChainRegistry +) : ProxyConstantsRepository { + + override suspend fun getDepositConstants(chainId: ChainId): DepositBaseAndFactor { + val runtime = chainRegestry.getRuntime(chainId) + val constantQuery = runtime.metadata.proxy() + return DepositBaseAndFactor( + baseAmount = constantQuery.numberConstant("ProxyDepositBase", runtime), + factorAmount = constantQuery.numberConstant("ProxyDepositFactor", runtime) + ) + } +} diff --git a/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureComponent.kt b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureComponent.kt new file mode 100644 index 0000000000..51109907b3 --- /dev/null +++ b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureComponent.kt @@ -0,0 +1,35 @@ +package io.novafoundation.nova.feature_proxy_impl.di + +import dagger.Component +import io.novafoundation.nova.common.di.CommonApi +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi +import io.novafoundation.nova.runtime.di.RuntimeApi + +@Component( + dependencies = [ + ProxyFeatureDependencies::class + ], + modules = [ + ProxyFeatureModule::class, + ] +) +@FeatureScope +interface ProxyFeatureComponent : ProxyFeatureApi { + + @Component.Factory + interface Factory { + + fun create( + deps: ProxyFeatureDependencies + ): ProxyFeatureComponent + } + + @Component( + dependencies = [ + CommonApi::class, + RuntimeApi::class + ] + ) + interface VoteFeatureDependenciesComponent : ProxyFeatureDependencies +} diff --git a/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureDependencies.kt b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureDependencies.kt new file mode 100644 index 0000000000..15efb8b851 --- /dev/null +++ b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureDependencies.kt @@ -0,0 +1,18 @@ +package io.novafoundation.nova.feature_proxy_impl.di + +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.storage.source.StorageDataSource +import javax.inject.Named + +interface ProxyFeatureDependencies { + + @Named(REMOTE_STORAGE_SOURCE) + fun remoteStorageSource(): StorageDataSource + + @Named(LOCAL_STORAGE_SOURCE) + fun localStorageSource(): StorageDataSource + + fun chainRegistry(): ChainRegistry +} diff --git a/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureHolder.kt b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureHolder.kt new file mode 100644 index 0000000000..24efb1ce8e --- /dev/null +++ b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureHolder.kt @@ -0,0 +1,22 @@ +package io.novafoundation.nova.feature_proxy_impl.di + +import io.novafoundation.nova.common.di.FeatureApiHolder +import io.novafoundation.nova.common.di.FeatureContainer +import io.novafoundation.nova.common.di.scope.ApplicationScope +import io.novafoundation.nova.runtime.di.RuntimeApi +import javax.inject.Inject + +@ApplicationScope +class ProxyFeatureHolder @Inject constructor( + featureContainer: FeatureContainer +) : FeatureApiHolder(featureContainer) { + + override fun initializeDependencies(): Any { + val dependencies = DaggerProxyFeatureComponent_VoteFeatureDependenciesComponent.builder() + .commonApi(commonApi()) + .runtimeApi(getFeature(RuntimeApi::class.java)) + .build() + return DaggerProxyFeatureComponent.factory() + .create(dependencies) + } +} diff --git a/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureModule.kt b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureModule.kt new file mode 100644 index 0000000000..a73d53db6d --- /dev/null +++ b/feature-proxy-impl/src/main/java/io/novafoundation/nova/feature_proxy_impl/di/ProxyFeatureModule.kt @@ -0,0 +1,49 @@ +package io.novafoundation.nova.feature_proxy_impl.di + +import dagger.Module +import dagger.Provides +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.feature_proxy_api.data.common.ProxyDepositCalculator +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository +import io.novafoundation.nova.feature_proxy_impl.data.common.RealProxyDepositCalculator +import io.novafoundation.nova.feature_proxy_impl.data.repository.RealGetProxyRepository +import io.novafoundation.nova.feature_proxy_impl.data.repository.RealProxyConstantsRepository +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.storage.source.StorageDataSource +import javax.inject.Named + +@Module +class ProxyFeatureModule { + + @Provides + @FeatureScope + fun provideProxyRepository( + @Named(REMOTE_STORAGE_SOURCE) remoteSource: StorageDataSource, + @Named(LOCAL_STORAGE_SOURCE) localSource: StorageDataSource, + chainRegistry: ChainRegistry + ): GetProxyRepository = RealGetProxyRepository( + remoteSource = remoteSource, + localSource = localSource, + chainRegistry + ) + + @Provides + @FeatureScope + fun provideProxyConstantsRepository( + chainRegistry: ChainRegistry + ): ProxyConstantsRepository = RealProxyConstantsRepository( + chainRegistry + ) + + @Provides + @FeatureScope + fun provideProxyDepositCalculator( + chainRegistry: ChainRegistry, + proxyConstantsRepository: ProxyConstantsRepository + ): ProxyDepositCalculator { + return RealProxyDepositCalculator(chainRegistry, proxyConstantsRepository) + } +} diff --git a/feature-proxy-impl/src/test/java/io/novafoundation/nova/feature_proxy_impl/ProxyGraphConstryctorTest.kt b/feature-proxy-impl/src/test/java/io/novafoundation/nova/feature_proxy_impl/ProxyGraphConstryctorTest.kt new file mode 100644 index 0000000000..12cf70d3aa --- /dev/null +++ b/feature-proxy-impl/src/test/java/io/novafoundation/nova/feature_proxy_impl/ProxyGraphConstryctorTest.kt @@ -0,0 +1,250 @@ +package io.novafoundation.nova.feature_proxy_impl + +import io.novafoundation.nova.common.address.AccountIdKey +import io.novafoundation.nova.common.utils.mapToSet +import io.novafoundation.nova.feature_proxy_api.data.common.NestedProxiesGraphConstructor.Node +import io.novafoundation.nova.feature_proxy_api.data.model.ProxyPermission +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.feature_proxy_impl.data.common.RealNestedProxiesGraphConstructor +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test + +class ProxyGraphConstryctorTest { + + @Test + fun test_result_size() { + val engine = RealNestedProxiesGraphConstructor( + startAccountIds = keysOf("Account_1", "Account_2"), + permissions = listOf(makePermission(from = "Account_4", to = "Account_1", ProxyType.Any)) + ) + + val result = engine.build() + + assertEquals(result.size, 2) + } + + @Test + fun simple_nested_proxies() { + val engine = RealNestedProxiesGraphConstructor( + startAccountIds = keysOf("Account_1", "Account_2"), + permissions = listOf( + makePermission(from = "Account_4", to = "Account_3", ProxyType.Any), + makePermission(from = "Account_3", to = "Account_1", ProxyType.Any), + ) + ) + + val result = engine.build() + + assertEquals( + result, + listOf( + makeNode( + accountId = "Account_1", + nestedNodes = makeSingleNode( + accountId = "Account_3", + nestedNodes = makeSingleNode( + "Account_4", + path = pathWithAny("Account_1", "Account_3") + ), + path = pathWithAny("Account_1") + ) + ), + makeNode("Account_2") + ) + ) + } + + // Set a timeout to check that we don't have an infinite loop + @Test(timeout = 10000L) + fun cyclical_nested_proxies() { + val engine = RealNestedProxiesGraphConstructor( + startAccountIds = keysOf("Account_1"), + permissions = listOf( + makePermission(from = "Account_3", to = "Account_2", ProxyType.Any), + makePermission(from = "Account_2", to = "Account_1", ProxyType.Any), + makePermission(from = "Account_1", to = "Account_3", ProxyType.Any), + ) + ) + + val result = engine.build() + + assertEquals( + result, + makeSingleNode( + accountId = "Account_1", + nestedNodes = makeSingleNode( + accountId = "Account_2", + nestedNodes = makeSingleNode( + accountId = "Account_3", + path = pathWithAny("Account_1", "Account_2") + ), + path = pathWithAny("Account_1") + ) + ) + ) + } + + @Test + fun mutually_exclusive_path_doesnt_build() { + val engine = RealNestedProxiesGraphConstructor( + startAccountIds = keysOf("Account_1"), + permissions = listOf( + // Staking is not controllable from Governance and should be skipped + makePermission(from = "Account_4", to = "Account_3", ProxyType.Staking), + makePermission(from = "Account_3", to = "Account_2", ProxyType.Any), + makePermission(from = "Account_2", to = "Account_1", ProxyType.Governance) + ) + ) + + val result = engine.build() + + assertEquals( + result, + makeSingleNode( + accountId = "Account_1", + nestedNodes = makeSingleNode( + accountId = "Account_2", + permissionType = ProxyType.Governance, + nestedNodes = makeSingleNode( + accountId = "Account_3", + path = path("Account_1" to ProxyType.Any, "Account_2" to ProxyType.Governance) + ), + path = pathWithAny("Account_1") + ) + ) + ) + } + + @Test + fun two_accounts_in_permission_chain() { + val engine = RealNestedProxiesGraphConstructor( + startAccountIds = keysOf("Account_1", "Account_2"), + permissions = listOf( + makePermission(from = "Account_3", to = "Account_2", ProxyType.Any), + makePermission(from = "Account_2", to = "Account_1", ProxyType.Any) + ) + ) + + val result = engine.build() + + assertEquals( + result, + listOf( + makeNode( + accountId = "Account_1", + nestedNodes = makeSingleNode( + accountId = "Account_2", + nestedNodes = makeSingleNode( + accountId = "Account_3", + path = pathWithAny("Account_1", "Account_2") + ), + path = pathWithAny("Account_1") + ) + ), + makeNode( + accountId = "Account_2", + nestedNodes = makeSingleNode( + accountId = "Account_3", + path = pathWithAny("Account_2") + ) + ) + ) + ) + } + + + @Test + fun cyclical_with_two_accounts_in_permission_chain() { + val engine = RealNestedProxiesGraphConstructor( + startAccountIds = keysOf("Account_1", "Account_2"), + permissions = listOf( + makePermission(from = "Account_3", to = "Account_2", ProxyType.Any), + makePermission(from = "Account_2", to = "Account_1", ProxyType.Any), + makePermission(from = "Account_1", to = "Account_3", ProxyType.Any), + makePermission(from = "Account_3", to = "Account_1", ProxyType.Any), + ) + ) + + val result = engine.build() + + assertEquals( + result, + listOf( + makeNode( + accountId = "Account_1", + nestedNodes = mutableListOf( + makeNode( + accountId = "Account_2", + nestedNodes = makeSingleNode( + accountId = "Account_3", + path = pathWithAny("Account_1", "Account_2") + ), + path = pathWithAny("Account_1") + ), + makeNode( + accountId = "Account_3", + path = pathWithAny("Account_1") + ) + ) + ), + makeNode( + accountId = "Account_2", + nestedNodes = makeSingleNode( + accountId = "Account_3", + nestedNodes = makeSingleNode( + accountId = "Account_1", + path = pathWithAny("Account_2", "Account_3") + ), + path = pathWithAny("Account_2") + ) + ) + ) + ) + } + + private fun keysOf(vararg values: String): Set { + return values.toList() + .mapToSet { it.intoKey() } + } + + private fun String.intoKey(): AccountIdKey { + return AccountIdKey(this.toByteArray()) + } + + private fun makePermission(from: String, to: String, type: ProxyType): ProxyPermission { + return ProxyPermission(from.intoKey(), to.intoKey(), type) + } + + private fun makeSingleNode( + accountId: String, + permissionType: ProxyType = ProxyType.Any, + nestedNodes: List = listOf(), + path: Map = mapOf() + ): MutableList { + return mutableListOf(makeNode(accountId, permissionType, nestedNodes, path)) + } + + private fun makeNode( + accountId: String, + permissionType: ProxyType = ProxyType.Any, + nestedNodes: List = listOf(), + path: Map = mapOf() + ): Node { + return Node(accountId.intoKey(), permissionType, nestedNodes, path) + } + + private fun pathWithAny( + vararg path: String + ): Map { + return path.associateBy { it.intoKey() } + .mapValues { ProxyType.Any } + } + + private fun path( + vararg path: Pair + ): Map { + return path.toMap() + .mapKeys { (accountId, _) -> accountId.intoKey() } + } +} diff --git a/feature-staking-api/build.gradle b/feature-staking-api/build.gradle index edfa58ff43..92bf8e2f78 100644 --- a/feature-staking-api/build.gradle +++ b/feature-staking-api/build.gradle @@ -31,6 +31,7 @@ dependencies { implementation project(':runtime') implementation project(':common') + implementation project(':feature-proxy-api') api project(":feature-wallet-api") api project(":feature-account-api") 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 2a89071eb4..c36f1943ac 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 @@ -19,7 +19,7 @@ sealed class StakingState( chainAsset: Chain.Asset, val accountId: AccountId, val controllerId: AccountId, - val stashId: AccountId, + val stashId: AccountId ) : StakingState(chain, chainAsset) { val accountAddress: String = chain.addressOf(accountId) @@ -58,3 +58,5 @@ sealed class StakingState( fun StakingState.Stash.stashTransactionOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(stashId) fun StakingState.Stash.controllerTransactionOrigin(): TransactionOrigin = TransactionOrigin.WalletWithAccount(controllerId) + +fun StakingState.Stash.accountIsStash(): Boolean = accountId.contentEquals(stashId) diff --git a/feature-staking-impl/build.gradle b/feature-staking-impl/build.gradle index d816e347f3..ede505cf2f 100644 --- a/feature-staking-impl/build.gradle +++ b/feature-staking-impl/build.gradle @@ -46,6 +46,7 @@ dependencies { implementation project(':feature-currency-api') implementation project(':feature-ledger-api') implementation project(':feature-dapp-api') + implementation project(':feature-proxy-api') implementation project(':runtime') implementation kotlinDep diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/ProxiesUpdater.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/ProxiesUpdater.kt new file mode 100644 index 0000000000..993fb4fa05 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/network/blockhain/updaters/ProxiesUpdater.kt @@ -0,0 +1,26 @@ +package io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters + +import io.novafoundation.nova.common.utils.proxyOrNull +import io.novafoundation.nova.core.storage.StorageCache +import io.novafoundation.nova.core_db.model.AccountStakingLocal +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.scope.AccountStakingScope +import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry +import io.novafoundation.nova.runtime.network.updaters.SingleStorageKeyUpdater +import jp.co.soramitsu.fearless_utils.runtime.RuntimeSnapshot +import jp.co.soramitsu.fearless_utils.runtime.metadata.storageKey +import jp.co.soramitsu.fearless_utils.runtime.metadata.storageOrNull + +class ProxiesUpdater( + scope: AccountStakingScope, + stakingSharedState: StakingSharedState, + chainRegistry: ChainRegistry, + storageCache: StorageCache +) : SingleStorageKeyUpdater(scope, stakingSharedState, chainRegistry, storageCache), StakingUpdater { + + override suspend fun storageKey(runtime: RuntimeSnapshot, scopeValue: AccountStakingLocal): String? { + val accountId = scopeValue.accountId + return runtime.metadata.proxyOrNull()?.storageOrNull("Proxies")?.storageKey(runtime, accountId) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureComponent.kt index 13bae0259c..d5c87e53a6 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureComponent.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureComponent.kt @@ -6,7 +6,9 @@ import io.novafoundation.nova.common.di.CommonApi import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi +import io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.StakingUpdateSystem @@ -56,8 +58,12 @@ import io.novafoundation.nova.feature_staking_impl.presentation.pools.searchPool import io.novafoundation.nova.feature_staking_impl.presentation.pools.selectPool.di.SelectPoolComponent import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm.di.ConfirmBondMoreComponent import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.select.di.SelectBondMoreComponent -import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm.di.ConfirmSetControllerComponent -import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.set.di.SetControllerComponent +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.di.ConfirmSetControllerComponent +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.set.di.SetControllerComponent +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.di.ConfirmAddStakingProxyComponent +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.set.di.AddStakingProxyComponent +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.di.StakingProxyListComponent +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.di.ConfirmRemoveStakingProxyComponent import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.di.StakingComponent import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.confirm.di.ConfirmRebondComponent import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.custom.di.CustomRebondComponent @@ -165,8 +171,16 @@ interface StakingFeatureComponent : StakingFeatureApi { fun setControllerFactory(): SetControllerComponent.Factory + fun setStakingProxyFactory(): AddStakingProxyComponent.Factory + + fun stakingProxyListFactory(): StakingProxyListComponent.Factory + fun confirmSetControllerFactory(): ConfirmSetControllerComponent.Factory + fun confirmAddStakingProxyFactory(): ConfirmAddStakingProxyComponent.Factory + + fun confirmRevokeStakingProxyFactory(): ConfirmRemoveStakingProxyComponent.Factory + fun rebondCustomFactory(): CustomRebondComponent.Factory fun currentValidatorsFactory(): CurrentValidatorsComponent.Factory @@ -226,6 +240,7 @@ interface StakingFeatureComponent : StakingFeatureApi { @BindsInstance parachainStaking: ParachainStakingRouter, @BindsInstance selectCollatorInterScreenCommunicator: SelectCollatorInterScreenCommunicator, @BindsInstance selectCollatorSettingsInterScreenCommunicator: SelectCollatorSettingsInterScreenCommunicator, + @BindsInstance selectAddressCommunicator: SelectAddressCommunicator, @BindsInstance nominationPoolsRouter: NominationPoolsRouter, @@ -242,6 +257,7 @@ interface StakingFeatureComponent : StakingFeatureApi { DbApi::class, RuntimeApi::class, AccountFeatureApi::class, + ProxyFeatureApi::class, WalletFeatureApi::class, DAppFeatureApi::class ] 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 8703881f96..e2c51faccc 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 @@ -16,6 +16,7 @@ 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.common.view.bottomSheet.description.DescriptionBottomSheetLauncher import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core_db.dao.AccountStakingDao import io.novafoundation.nova.core_db.dao.ExternalBalanceDao @@ -23,15 +24,24 @@ import io.novafoundation.nova.core_db.dao.StakingDashboardDao import io.novafoundation.nova.core_db.dao.StakingRewardPeriodDao import io.novafoundation.nova.core_db.dao.StakingTotalRewardDao 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.domain.account.identity.IdentityProvider +import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity 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.interfaces.SelectedAccountUseCase import io.novafoundation.nova.feature_account_api.domain.updaters.AccountUpdateScope import io.novafoundation.nova.feature_account_api.presenatation.account.AddressDisplayUseCase import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory import io.novafoundation.nova.feature_account_api.presenatation.mixin.identity.IdentityMixin +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository +import io.novafoundation.nova.feature_proxy_api.data.common.ProxyDepositCalculator +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository import io.novafoundation.nova.feature_wallet_api.data.cache.AssetCache import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository import io.novafoundation.nova.feature_wallet_api.domain.ArbitraryAssetUseCase @@ -109,6 +119,9 @@ interface StakingFeatureDependencies { @Named(LOCAL_STORAGE_SOURCE) fun localStorageSource(): StorageDataSource + @LocalIdentity + fun localIdentity(): IdentityProvider + fun chainRegistry(): ChainRegistry fun imageLoader(): ImageLoader @@ -123,6 +136,8 @@ interface StakingFeatureDependencies { fun enoughTotalToStayAboveEDValidationFactory(): EnoughTotalToStayAboveEDValidationFactory + fun addressInputMixinFactory(): AddressInputMixinFactory + val amountChooserMixinFactory: AmountChooserMixin.Factory val actionAwaitableMixinFactory: ActionAwaitableMixin.Factory @@ -163,4 +178,18 @@ interface StakingFeatureDependencies { val externalBalanceDao: ExternalBalanceDao val partialRetriableMixinFactory: PartialRetriableMixin.Factory + + val proxyDepositCalculator: ProxyDepositCalculator + + val getProxyRepository: GetProxyRepository + + val descriptionBottomSheetLauncher: DescriptionBottomSheetLauncher + + val metaAccountGroupingInteractor: MetaAccountGroupingInteractor + + val selectAddressMixinFactory: SelectAddressMixin.Factory + + val proxyConstantsRepository: ProxyConstantsRepository + + val proxySyncService: ProxySyncService } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureHolder.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureHolder.kt index ef40639987..6bcf63234e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureHolder.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingFeatureHolder.kt @@ -5,7 +5,9 @@ import io.novafoundation.nova.common.di.FeatureContainer import io.novafoundation.nova.common.di.scope.ApplicationScope import io.novafoundation.nova.core_db.di.DbApi import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressCommunicator import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi +import io.novafoundation.nova.feature_proxy_api.di.ProxyFeatureApi import io.novafoundation.nova.feature_staking_impl.presentation.NominationPoolsRouter import io.novafoundation.nova.feature_staking_impl.presentation.ParachainStakingRouter import io.novafoundation.nova.feature_staking_impl.presentation.StakingDashboardRouter @@ -25,6 +27,7 @@ class StakingFeatureHolder @Inject constructor( private val nominationPoolsRouter: NominationPoolsRouter, private val startMultiStakingRouter: StartMultiStakingRouter, private val stakingDashboardRouter: StakingDashboardRouter, + private val selectAddressCommunicator: SelectAddressCommunicator, private val selectCollatorInterScreenCommunicator: SelectCollatorInterScreenCommunicator, private val selectCollatorSettingsInterScreenCommunicator: SelectCollatorSettingsInterScreenCommunicator, ) : FeatureApiHolder(featureContainer) { @@ -36,6 +39,7 @@ class StakingFeatureHolder @Inject constructor( .dbApi(getFeature(DbApi::class.java)) .walletFeatureApi(getFeature(WalletFeatureApi::class.java)) .accountFeatureApi(getFeature(AccountFeatureApi::class.java)) + .proxyFeatureApi(getFeature(ProxyFeatureApi::class.java)) .dAppFeatureApi(getFeature(DAppFeatureApi::class.java)) .build() @@ -45,6 +49,7 @@ class StakingFeatureHolder @Inject constructor( parachainStaking = parachainStakingRouter, selectCollatorInterScreenCommunicator = selectCollatorInterScreenCommunicator, selectCollatorSettingsInterScreenCommunicator = selectCollatorSettingsInterScreenCommunicator, + selectAddressCommunicator = selectAddressCommunicator, nominationPoolsRouter = nominationPoolsRouter, startMultiStakingRouter = startMultiStakingRouter, stakingDashboardRouter = stakingDashboardRouter, 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 d768208a63..00330dc08f 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 @@ -15,12 +15,19 @@ import io.novafoundation.nova.core_db.dao.ExternalBalanceDao import io.novafoundation.nova.core_db.dao.StakingRewardPeriodDao import io.novafoundation.nova.core_db.dao.StakingTotalRewardDao 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.domain.account.identity.IdentityProvider +import io.novafoundation.nova.feature_account_api.domain.account.identity.LocalIdentity import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository import io.novafoundation.nova.feature_account_api.domain.updaters.AccountUpdateScope import io.novafoundation.nova.feature_account_api.presenatation.account.AddressDisplayUseCase +import io.novafoundation.nova.feature_proxy_api.data.common.ProxyDepositCalculator +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository import io.novafoundation.nova.feature_staking_api.data.network.blockhain.updaters.PooledBalanceUpdaterFactory import io.novafoundation.nova.feature_staking_api.data.nominationPools.pool.PoolAccountDerivation +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.AddStakingProxyInteractor import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_api.presentation.nominationPools.display.PoolDisplayUseCase import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState @@ -30,6 +37,7 @@ import io.novafoundation.nova.feature_staking_impl.data.network.subquery.SubQuer import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.updater.RealPooledBalanceUpdaterFactory import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.NominationPoolStateRepository import io.novafoundation.nova.feature_staking_impl.data.parachainStaking.RoundDurationEstimator +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.RealAddStakingProxyInteractor import io.novafoundation.nova.feature_staking_impl.data.repository.BagListRepository import io.novafoundation.nova.feature_staking_impl.data.repository.LocalBagListRepository import io.novafoundation.nova.feature_staking_impl.data.repository.ParasRepository @@ -74,7 +82,11 @@ import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settin import io.novafoundation.nova.feature_staking_impl.domain.rewards.RewardCalculatorFactory import io.novafoundation.nova.feature_staking_impl.domain.setup.ChangeValidatorsInteractor import io.novafoundation.nova.feature_staking_impl.domain.staking.bond.BondMoreInteractor -import io.novafoundation.nova.feature_staking_impl.domain.staking.controller.ControllerInteractor +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.controller.ControllerInteractor +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.list.RealStakingProxyListInteractor +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.list.StakingProxyListInteractor +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.remove.RealRemoveStakingProxyInteractor +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.remove.RemoveStakingProxyInteractor import io.novafoundation.nova.feature_staking_impl.domain.staking.rebond.RebondInteractor import io.novafoundation.nova.feature_staking_impl.domain.staking.redeem.RedeemInteractor import io.novafoundation.nova.feature_staking_impl.domain.staking.rewardDestination.ChangeRewardDestinationInteractor @@ -602,4 +614,42 @@ class StakingFeatureModule { accountRepository = accountRepository, chainRegistry = chainRegistry ) + + @Provides + @FeatureScope + fun provideAddProxyRepository( + extrinsicService: ExtrinsicService, + proxyDepositCalculator: ProxyDepositCalculator, + getProxyRepository: GetProxyRepository, + proxyConstantsRepository: ProxyConstantsRepository, + proxySyncService: ProxySyncService + ): AddStakingProxyInteractor { + return RealAddStakingProxyInteractor( + extrinsicService, + proxyDepositCalculator, + getProxyRepository, + proxyConstantsRepository, + proxySyncService + ) + } + + @Provides + @FeatureScope + fun provideStakingProxyListInteractor( + getProxyRepository: GetProxyRepository, + @LocalIdentity identityProvider: IdentityProvider + ): StakingProxyListInteractor = RealStakingProxyListInteractor( + getProxyRepository, + identityProvider + ) + + @Provides + @FeatureScope + fun removeStakingProxyInteractor( + extrinsicService: ExtrinsicService, + proxySyncService: ProxySyncService + ): RemoveStakingProxyInteractor = RealRemoveStakingProxyInteractor( + extrinsicService, + proxySyncService + ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingValidationModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingValidationModule.kt index aa47d956bb..431d5ed12c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingValidationModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/StakingValidationModule.kt @@ -1,10 +1,12 @@ package io.novafoundation.nova.feature_staking_impl.di import dagger.Module +import io.novafoundation.nova.feature_staking_impl.di.validations.AddStakingProxyValidationsModule import io.novafoundation.nova.feature_staking_impl.di.validations.BondMoreValidationsModule import io.novafoundation.nova.feature_staking_impl.di.validations.MakePayoutValidationsModule import io.novafoundation.nova.feature_staking_impl.di.validations.RebondValidationsModule import io.novafoundation.nova.feature_staking_impl.di.validations.RedeemValidationsModule +import io.novafoundation.nova.feature_staking_impl.di.validations.RemoveStakingProxyValidationsModule import io.novafoundation.nova.feature_staking_impl.di.validations.RewardDestinationValidationsModule import io.novafoundation.nova.feature_staking_impl.di.validations.SetControllerValidationsModule import io.novafoundation.nova.feature_staking_impl.di.validations.SetupStakingValidationsModule @@ -21,7 +23,9 @@ import io.novafoundation.nova.feature_staking_impl.di.validations.UnbondValidati RebondValidationsModule::class, SetControllerValidationsModule::class, RewardDestinationValidationsModule::class, - StakeActionsValidationModule::class + StakeActionsValidationModule::class, + AddStakingProxyValidationsModule::class, + RemoveStakingProxyValidationsModule::class ] ) class StakingValidationModule 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 7b8aa8fe4e..bdd3f4039e 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 @@ -23,6 +23,7 @@ import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.update import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.MaxNominatorsUpdater import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.MinBondUpdater import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.ParachainsUpdater +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.ProxiesUpdater import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.StakingLedgerUpdater import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.StakingUpdaters import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.ValidatorExposureUpdater @@ -360,6 +361,20 @@ class RelaychainStakingUpdatersModule { chainRegistry, ) + @Provides + @FeatureScope + fun provideProxiesUpdater( + storageCache: StorageCache, + scope: AccountStakingScope, + sharedState: StakingSharedState, + chainRegistry: ChainRegistry, + ) = ProxiesUpdater( + scope, + sharedState, + chainRegistry, + storageCache, + ) + @Provides @Relaychain @FeatureScope @@ -385,6 +400,7 @@ class RelaychainStakingUpdatersModule { genesisSlotUpdater: GenesisSlotUpdater, currentSessionIndexUpdater: CurrentSessionIndexUpdater, eraStartSessionIndexUpdater: EraStartSessionIndexUpdater, + proxiesUpdater: ProxiesUpdater ) = StakingUpdaters.Group( activeEraUpdater, validatorExposureUpdater, @@ -406,6 +422,7 @@ class RelaychainStakingUpdatersModule { currentSlotUpdater, genesisSlotUpdater, currentSessionIndexUpdater, - eraStartSessionIndexUpdater + eraStartSessionIndexUpdater, + proxiesUpdater ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/AddStakingProxyValidationsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/AddStakingProxyValidationsModule.kt new file mode 100644 index 0000000000..6e15dd850f --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/AddStakingProxyValidationsModule.kt @@ -0,0 +1,43 @@ +package io.novafoundation.nova.feature_staking_impl.di.validations + +import dagger.Module +import dagger.Provides +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.enoughBalanceToPayDepositAndFee +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.maximumProxies +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.notSelfAccount +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.stakingTypeIsNotDuplication +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.sufficientBalanceToPayFee +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.sufficientBalanceToStayAboveEd +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.validAddress +import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory + +@Module +class AddStakingProxyValidationsModule { + + @FeatureScope + @Provides + fun provideAddStakingProxyValidationSystem( + getProxyRepository: GetProxyRepository, + accountRepository: AccountRepository, + enoughTotalToStayAboveEDValidationFactory: EnoughTotalToStayAboveEDValidationFactory + ): AddStakingProxyValidationSystem = ValidationSystem { + validAddress() + + notSelfAccount(accountRepository) + + sufficientBalanceToPayFee() + + sufficientBalanceToStayAboveEd(enoughTotalToStayAboveEDValidationFactory) + + stakingTypeIsNotDuplication(getProxyRepository) + + maximumProxies(getProxyRepository) + + enoughBalanceToPayDepositAndFee() + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RemoveStakingProxyValidationsModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RemoveStakingProxyValidationsModule.kt new file mode 100644 index 0000000000..d956efa241 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/RemoveStakingProxyValidationsModule.kt @@ -0,0 +1,26 @@ +package io.novafoundation.nova.feature_staking_impl.di.validations + +import dagger.Module +import dagger.Provides +import io.novafoundation.nova.common.di.scope.FeatureScope +import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.sufficientBalanceToPayFee +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.sufficientBalanceToStayAboveEd +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.RemoveStakingProxyValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.sufficientBalanceToPayFee +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.sufficientBalanceToStayAboveEd +import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory + +@Module +class RemoveStakingProxyValidationsModule { + + @FeatureScope + @Provides + fun provideAddStakingProxyValidationSystem( + enoughTotalToStayAboveEDValidationFactory: EnoughTotalToStayAboveEDValidationFactory + ): RemoveStakingProxyValidationSystem = ValidationSystem { + sufficientBalanceToPayFee() + + sufficientBalanceToStayAboveEd(enoughTotalToStayAboveEDValidationFactory) + } +} 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 cab15bab83..6ebd5a50fb 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 @@ -7,10 +7,10 @@ import io.novafoundation.nova.common.validation.CompositeValidation import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.domain.validations.NotZeroBalanceValidation -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.IsNotControllerAccountValidation -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerFeeValidation -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationFailure -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.IsNotControllerAccountValidation +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerFeeValidation +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationFailure +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationSystem import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughAmountToTransferValidation diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/StakeActionsValidationModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/StakeActionsValidationModule.kt index 65bc197a0e..0ef5a5cd07 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/StakeActionsValidationModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/validations/StakeActionsValidationModule.kt @@ -11,10 +11,14 @@ import io.novafoundation.nova.common.validation.ValidationSystem 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_impl.data.StakingSharedState +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.BALANCE_CONTROLLER_IS_NOT_ALLOWED import io.novafoundation.nova.feature_staking_impl.domain.validations.main.BALANCE_REQUIRED_CONTROLLER -import io.novafoundation.nova.feature_staking_impl.domain.validations.main.BALANCE_REQUIRED_STASH -import io.novafoundation.nova.feature_staking_impl.domain.validations.main.MainStakingAccountRequiredValidation +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.BALANCE_REQUIRED_STASH_META_ACCOUNT +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.ControllerAccountIsNotAllowedValidation +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.MainStakingMetaAccountRequiredValidation import io.novafoundation.nova.feature_staking_impl.domain.validations.main.MainStakingUnlockingLimitValidation +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_ADD_PROXY +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_PROXIES import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_REWARD_DESTINATION import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_STAKING_BOND_MORE import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_STAKING_REBAG @@ -40,7 +44,7 @@ class StakeActionsValidationsModule { fun provideControllerValidation( stakingSharedState: StakingSharedState, accountRepository: AccountRepository - ) = MainStakingAccountRequiredValidation( + ) = MainStakingMetaAccountRequiredValidation( accountRepository, accountAddressExtractor = { it.stashState.controllerAddress }, errorProducer = StakeActionsValidationFailure::ControllerRequired, @@ -48,18 +52,31 @@ class StakeActionsValidationsModule { ) @FeatureScope - @Named(BALANCE_REQUIRED_STASH) + @Named(BALANCE_REQUIRED_STASH_META_ACCOUNT) @Provides fun provideStashValidation( stakingSharedState: StakingSharedState, accountRepository: AccountRepository - ) = MainStakingAccountRequiredValidation( + ) = MainStakingMetaAccountRequiredValidation( accountRepository, accountAddressExtractor = { it.stashState.stashAddress }, errorProducer = StakeActionsValidationFailure::StashRequired, sharedState = stakingSharedState ) + @FeatureScope + @Named(BALANCE_CONTROLLER_IS_NOT_ALLOWED) + @Provides + fun provideControllerNotAllowedValidation( + stakingSharedState: StakingSharedState, + accountRepository: AccountRepository + ) = ControllerAccountIsNotAllowedValidation( + accountRepository, + stakingState = { it.stashState }, + errorProducer = StakeActionsValidationFailure::StashRequiredToManageProxies, + sharedState = stakingSharedState + ) + @FeatureScope @Provides fun provideUnbondingLimitValidation( @@ -77,7 +94,7 @@ class StakeActionsValidationsModule { @Provides fun provideRedeemValidationSystem( @Named(BALANCE_REQUIRED_CONTROLLER) - controllerRequiredValidation: MainStakingAccountRequiredValidation, + controllerRequiredValidation: MainStakingMetaAccountRequiredValidation, ) = StakeActionsValidationSystem( CompositeValidation( validations = listOf( @@ -86,12 +103,28 @@ class StakeActionsValidationsModule { ) ) + @FeatureScope + @Named(SYSTEM_MANAGE_PROXIES) + @Provides + fun provideManageProxiesValidationSystem( + @Named(BALANCE_CONTROLLER_IS_NOT_ALLOWED) + controllerAccountIsNotAllowedValidation: ControllerAccountIsNotAllowedValidation, + ) = StakeActionsValidationSystem(controllerAccountIsNotAllowedValidation) + + @FeatureScope + @Named(SYSTEM_ADD_PROXY) + @Provides + fun provideAddProxiesValidationSystem( + @Named(BALANCE_CONTROLLER_IS_NOT_ALLOWED) + controllerAccountIsNotAllowedValidation: ControllerAccountIsNotAllowedValidation, + ) = StakeActionsValidationSystem(controllerAccountIsNotAllowedValidation) + @FeatureScope @Named(SYSTEM_MANAGE_STAKING_BOND_MORE) @Provides fun provideBondMoreValidationSystem( - @Named(BALANCE_REQUIRED_STASH) - stashRequiredValidation: MainStakingAccountRequiredValidation, + @Named(BALANCE_REQUIRED_STASH_META_ACCOUNT) + stashRequiredValidation: MainStakingMetaAccountRequiredValidation, ) = StakeActionsValidationSystem( CompositeValidation( validations = listOf( @@ -105,7 +138,7 @@ class StakeActionsValidationsModule { @Provides fun provideUnbondValidationSystem( @Named(BALANCE_REQUIRED_CONTROLLER) - controllerRequiredValidation: MainStakingAccountRequiredValidation, + controllerRequiredValidation: MainStakingMetaAccountRequiredValidation, balanceUnlockingLimitValidation: MainStakingUnlockingLimitValidation ) = StakeActionsValidationSystem( CompositeValidation( @@ -121,7 +154,7 @@ class StakeActionsValidationsModule { @Provides fun provideRebondValidationSystem( @Named(BALANCE_REQUIRED_CONTROLLER) - controllerRequiredValidation: MainStakingAccountRequiredValidation + controllerRequiredValidation: MainStakingMetaAccountRequiredValidation ) = StakeActionsValidationSystem( CompositeValidation( validations = listOf( @@ -134,8 +167,8 @@ class StakeActionsValidationsModule { @Named(SYSTEM_MANAGE_STAKING_REBAG) @Provides fun provideRebagValidationSystem( - @Named(BALANCE_REQUIRED_STASH) - stashRequiredValidation: MainStakingAccountRequiredValidation + @Named(BALANCE_REQUIRED_STASH_META_ACCOUNT) + stashRequiredValidation: MainStakingMetaAccountRequiredValidation ): StakeActionsValidationSystem = ValidationSystem { validate(stashRequiredValidation) } @@ -145,7 +178,7 @@ class StakeActionsValidationsModule { @Provides fun provideRewardDestinationValidationSystemToMap( @Named(BALANCE_REQUIRED_CONTROLLER) - controllerRequiredValidation: MainStakingAccountRequiredValidation, + controllerRequiredValidation: MainStakingMetaAccountRequiredValidation, ): StakeActionsValidationSystem = StakeActionsValidationSystem(controllerRequiredValidation) } @@ -175,4 +208,20 @@ interface StakeActionsValidationModule { fun provideUnbondValidationSystemToMap( @Named(SYSTEM_MANAGE_STAKING_UNBOND) system: StakeActionsValidationSystem, ): StakeActionsValidationSystem + + @FeatureScope + @StakeActionsValidationKey(SYSTEM_MANAGE_PROXIES) + @IntoMap + @Binds + fun provideManageProxyValidationSystemToMap( + @Named(SYSTEM_MANAGE_PROXIES) system: StakeActionsValidationSystem, + ): StakeActionsValidationSystem + + @FeatureScope + @StakeActionsValidationKey(SYSTEM_ADD_PROXY) + @IntoMap + @Binds + fun provideAddProxyValidationSystemToMap( + @Named(SYSTEM_ADD_PROXY) system: StakeActionsValidationSystem, + ): StakeActionsValidationSystem } 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 d871e1a84d..6c165a438a 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 @@ -12,7 +12,7 @@ fun ValidationSystem.Companion.rebagValidationSystem(): RebagValidationSystem = error = { context -> RebagValidationFailure.NotEnoughToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 d38c4dd878..4337303a81 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 @@ -44,7 +44,7 @@ private fun NominationPoolsClaimRewardsValidationSystemBuilder.enoughToPayFees() error = { context -> NominationPoolsClaimRewardsValidationFailure.NotEnoughBalanceToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 122a82aa81..9ff505d945 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 @@ -26,7 +26,7 @@ private fun NominationPoolsRedeemValidationSystemBuilder.enoughToPayFees() { error = { context -> NominationPoolsRedeemValidationFailure.NotEnoughBalanceToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 2da505dcf3..c866a640da 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 @@ -43,7 +43,7 @@ private fun NominationPoolsUnbondValidationSystemBuilder.enoughToPayFees() { error = { context -> NominationPoolsUnbondValidationFailure.NotEnoughBalanceToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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 19c494f6de..951755dded 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 @@ -19,7 +19,7 @@ fun ValidationSystem.Companion.yieldBoost( error = { context -> YieldBoostValidationFailure.NotEnoughToPayToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } 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/delegation/controller/ControllerInteractor.kt similarity index 99% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/controller/ControllerInteractor.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/controller/ControllerInteractor.kt index 690a2e0e0c..85d5f3b5fb 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/delegation/controller/ControllerInteractor.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.domain.staking.controller +package io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.controller import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.intoOrigin import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/AddStakingProxyInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/AddStakingProxyInteractor.kt new file mode 100644 index 0000000000..3bce3f08da --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/AddStakingProxyInteractor.kt @@ -0,0 +1,16 @@ +package io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy + +import io.novafoundation.nova.feature_account_api.data.model.Fee +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 jp.co.soramitsu.fearless_utils.runtime.AccountId + +interface AddStakingProxyInteractor { + + suspend fun estimateFee(chain: Chain, proxiedAccountId: AccountId): Fee + + suspend fun addProxy(chain: Chain, proxiedAccountId: AccountId, proxyAccountId: AccountId): Result + + suspend fun calculateDeltaDepositForAddProxy(chain: Chain, accountId: AccountId): Balance +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/RealAddStakingProxyInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/RealAddStakingProxyInteractor.kt new file mode 100644 index 0000000000..c99edce477 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/RealAddStakingProxyInteractor.kt @@ -0,0 +1,54 @@ +package io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy + +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.awaitInBlock +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_proxy_api.data.calls.addProxyCall +import io.novafoundation.nova.feature_proxy_api.data.common.ProxyDepositCalculator +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.data.repository.ProxyConstantsRepository +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.runtime.ext.emptyAccountId +import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class RealAddStakingProxyInteractor( + private val extrinsicService: ExtrinsicService, + private val proxyDepositCalculator: ProxyDepositCalculator, + private val getProxyRepository: GetProxyRepository, + private val proxyConstantsRepository: ProxyConstantsRepository, + private val proxySyncService: ProxySyncService +) : AddStakingProxyInteractor { + + override suspend fun estimateFee(chain: Chain, proxiedAccountId: AccountId): Fee { + return withContext(Dispatchers.IO) { + extrinsicService.estimateFee(chain, proxiedAccountId.intoOrigin()) { + addProxyCall(chain.emptyAccountId(), ProxyType.Staking) + } + } + } + + override suspend fun addProxy(chain: Chain, proxiedAccountId: AccountId, proxyAccountId: AccountId): Result { + return withContext(Dispatchers.Default) { + val result = extrinsicService.submitAndWatchExtrinsic(chain, proxiedAccountId.intoOrigin()) { + addProxyCall(proxyAccountId, ProxyType.Staking) + } + + result.awaitInBlock().also { proxySyncService.startSyncing() } + } + } + + override suspend fun calculateDeltaDepositForAddProxy(chain: Chain, accountId: AccountId): Balance { + val depositConstants = proxyConstantsRepository.getDepositConstants(chain.id) + val currentProxiesCount = getProxyRepository.getProxiesQuantity(chain.id, accountId) + val oldDeposit = proxyDepositCalculator.calculateProxyDepositForQuantity(depositConstants, currentProxiesCount) + val newDeposit = proxyDepositCalculator.calculateProxyDepositForQuantity(depositConstants, currentProxiesCount + 1) + return newDeposit - oldDeposit + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/list/RealStakingProxyListInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/list/RealStakingProxyListInteractor.kt new file mode 100644 index 0000000000..8a13aa97c4 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/list/RealStakingProxyListInteractor.kt @@ -0,0 +1,37 @@ +package io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.list + +import io.novafoundation.nova.feature_account_api.domain.account.identity.IdentityProvider +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.list.model.StakingProxyAccount +import io.novafoundation.nova.runtime.ext.addressOf +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +interface StakingProxyListInteractor { + fun stakingProxyListFlow(chain: Chain, accountId: AccountId): Flow> +} + +class RealStakingProxyListInteractor( + val getProxyRepository: GetProxyRepository, + val identityProvider: IdentityProvider +) : StakingProxyListInteractor { + + override fun stakingProxyListFlow(chain: Chain, accountId: AccountId): Flow> { + return getProxyRepository.proxiesByTypeFlow(chain, accountId, ProxyType.Staking) + .map { proxies -> + val proxiesAccountIds = proxies.map { it.proxyAccountId.value } + val proxyIdentities = identityProvider.identitiesFor(proxiesAccountIds, chain.id) + proxies.map { proxy -> + val proxyAccountId = proxy.proxyAccountId + val identity = proxyIdentities[proxyAccountId] + StakingProxyAccount( + identity?.name ?: chain.addressOf(proxyAccountId.value), + proxyAccountId.value + ) + } + } + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/list/StakingProxyListInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/list/StakingProxyListInteractor.kt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/list/model/StakingProxyAccount.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/list/model/StakingProxyAccount.kt new file mode 100644 index 0000000000..4361f339a4 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/list/model/StakingProxyAccount.kt @@ -0,0 +1,8 @@ +package io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.list.model + +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class StakingProxyAccount( + val accountName: String, + val proxyAccountId: AccountId +) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/remove/RemoveStakingProxyInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/remove/RemoveStakingProxyInteractor.kt new file mode 100644 index 0000000000..a293b1bce5 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/delegation/proxy/remove/RemoveStakingProxyInteractor.kt @@ -0,0 +1,46 @@ +package io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.remove + +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.awaitInBlock +import io.novafoundation.nova.feature_account_api.data.model.Fee +import io.novafoundation.nova.feature_account_api.data.proxy.ProxySyncService +import io.novafoundation.nova.feature_proxy_api.data.calls.removeProxyCall +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.runtime.ext.emptyAccountId +import io.novafoundation.nova.runtime.extrinsic.ExtrinsicStatus +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +interface RemoveStakingProxyInteractor { + + suspend fun estimateFee(chain: Chain, proxiedAccountId: AccountId): Fee + + suspend fun removeProxy(chain: Chain, proxiedAccountId: AccountId, proxyAccountId: AccountId): Result +} + +class RealRemoveStakingProxyInteractor( + private val extrinsicService: ExtrinsicService, + private val proxySyncService: ProxySyncService +) : RemoveStakingProxyInteractor { + + override suspend fun estimateFee(chain: Chain, proxiedAccountId: AccountId): Fee { + return withContext(Dispatchers.IO) { + extrinsicService.estimateFee(chain, proxiedAccountId.intoOrigin()) { + removeProxyCall(chain.emptyAccountId(), ProxyType.Staking) + } + } + } + + override suspend fun removeProxy(chain: Chain, proxiedAccountId: AccountId, proxyAccountId: AccountId): Result { + return withContext(Dispatchers.Default) { + val result = extrinsicService.submitAndWatchExtrinsic(chain, proxiedAccountId.intoOrigin()) { + removeProxyCall(proxyAccountId, ProxyType.Staking) + } + + result.awaitInBlock().also { proxySyncService.startSyncing() } + } + } +} 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 8f236cbe44..24be148225 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 @@ -117,7 +117,7 @@ private class DirectStakingProperties( error = { context -> StartMultiStakingValidationFailure.NotEnoughToPayFees( chainAsset = context.payload.asset.token.configuration, - maxUsable = context.availableToPayFees, + maxUsable = context.maxUsable, fee = context.fee ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/NotZeroBalanceValidation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/NotZeroBalanceValidation.kt index d0825b2dfe..ef2695d923 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/NotZeroBalanceValidation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/NotZeroBalanceValidation.kt @@ -4,8 +4,8 @@ import io.novafoundation.nova.common.validation.DefaultFailureLevel import io.novafoundation.nova.common.validation.Validation import io.novafoundation.nova.common.validation.ValidationStatus import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationFailure -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationPayload +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationFailure +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationPayload import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletRepository import io.novafoundation.nova.runtime.ext.accountIdOf import io.novafoundation.nova.runtime.state.chain diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/StashOnlyIsAllowedValidation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/StashOnlyIsAllowedValidation.kt new file mode 100644 index 0000000000..5d53a8a41a --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/StashOnlyIsAllowedValidation.kt @@ -0,0 +1,32 @@ +package io.novafoundation.nova.feature_staking_impl.domain.validations + +import io.novafoundation.nova.common.validation.DefaultFailureLevel +import io.novafoundation.nova.common.validation.Validation +import io.novafoundation.nova.common.validation.ValidationStatus +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_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.accountIsStash +import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState +import io.novafoundation.nova.runtime.state.chain + +class StashOnlyIsAllowedValidation( + val accountRepository: AccountRepository, + val stakingState: (P) -> StakingState, + val sharedState: StakingSharedState, + val errorProducer: (stashAddress: String, stashAccount: MetaAccount?) -> E +) : Validation { + + override suspend fun validate(value: P): ValidationStatus { + val stakingState = stakingState(value) + if (stakingState !is StakingState.Stash) throw IllegalStateException("StashOnlyIsAllowedValidation can be used only for Stash state") + + return if (stakingState.accountIsStash()) { + ValidationStatus.Valid() + } else { + val chain = sharedState.chain() + val stashMetaAccount = accountRepository.findMetaAccount(stakingState.stashId, chain.id) + ValidationStatus.NotValid(DefaultFailureLevel.ERROR, errorProducer(stakingState.stashAddress, stashMetaAccount)) + } + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/ChangeStackingValidationSystem.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/ChangeStackingValidationSystem.kt similarity index 98% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/ChangeStackingValidationSystem.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/ChangeStackingValidationSystem.kt index 2407497729..1aa56e3387 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/ChangeStackingValidationSystem.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/ChangeStackingValidationSystem.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.domain.validations.controller +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.common.validation.ValidationSystemBuilder diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/Declarations.kt similarity index 96% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/Declarations.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/Declarations.kt index 0227cae53d..d633bc5434 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/Declarations.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/Declarations.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.domain.validations.controller +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller import io.novafoundation.nova.common.validation.ValidationSystem import io.novafoundation.nova.feature_staking_impl.domain.validations.AccountIsNotControllerValidation diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/SetControllerValidationFailure.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/SetControllerValidationFailure.kt similarity index 86% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/SetControllerValidationFailure.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/SetControllerValidationFailure.kt index d3d51dd29e..98e784933e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/SetControllerValidationFailure.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/SetControllerValidationFailure.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.domain.validations.controller +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller enum class SetControllerValidationFailure { NOT_ENOUGH_TO_PAY_FEES, ALREADY_CONTROLLER, ZERO_CONTROLLER_BALANCE 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/delegation/controller/SetControllerValidationPayload.kt similarity index 92% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/controller/SetControllerValidationPayload.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/controller/SetControllerValidationPayload.kt index 4d35cf660c..c0b3046448 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/delegation/controller/SetControllerValidationPayload.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.domain.validations.controller +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller import io.novafoundation.nova.feature_wallet_api.presentation.model.DecimalFee import java.math.BigDecimal diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/AddStakingProxyValidationFailure.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/AddStakingProxyValidationFailure.kt new file mode 100644 index 0000000000..ec7cbcc86c --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/AddStakingProxyValidationFailure.kt @@ -0,0 +1,32 @@ +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add + +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.domain.validation.InsufficientBalanceToStayAboveEDError +import io.novafoundation.nova.feature_wallet_api.domain.validation.NotEnoughToPayFeesError +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import java.math.BigDecimal + +sealed interface AddStakingProxyValidationFailure { + + class NotEnoughToPayFee( + override val chainAsset: Chain.Asset, + override val maxUsable: BigDecimal, + override val fee: BigDecimal + ) : AddStakingProxyValidationFailure, NotEnoughToPayFeesError + + class NotEnoughToStayAboveED(override val asset: Chain.Asset) : AddStakingProxyValidationFailure, InsufficientBalanceToStayAboveEDError + + class NotEnoughBalanceToReserveDeposit( + val chainAsset: Chain.Asset, + val maxUsable: Balance, + val deposit: Balance + ) : AddStakingProxyValidationFailure + + class InvalidAddress(val chain: Chain) : AddStakingProxyValidationFailure + + object SelfDelegation : AddStakingProxyValidationFailure + + class MaximumProxiesReached(val chain: Chain, val max: Int) : AddStakingProxyValidationFailure + + class AlreadyDelegated(val address: String) : AddStakingProxyValidationFailure +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/AddStakingProxyValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/AddStakingProxyValidationPayload.kt new file mode 100644 index 0000000000..8a4dcb6ada --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/AddStakingProxyValidationPayload.kt @@ -0,0 +1,17 @@ +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add + +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.DecimalFee +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import jp.co.soramitsu.fearless_utils.runtime.AccountId + +class AddStakingProxyValidationPayload( + val chain: Chain, + val asset: Asset, + val proxiedAccountId: AccountId, + val proxyAddress: String, + val fee: DecimalFee, + val deltaDeposit: Balance, + val currentQuantity: Int +) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/Declarations.kt new file mode 100644 index 0000000000..f51ee4604e --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/add/Declarations.kt @@ -0,0 +1,106 @@ +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add + +import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.validation.notSelfAccount +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType +import io.novafoundation.nova.feature_proxy_api.domain.validators.maximumProxiesNotReached +import io.novafoundation.nova.feature_proxy_api.domain.validators.proxyIsNotDuplicationForAccount +import io.novafoundation.nova.feature_wallet_api.domain.model.amountFromPlanks +import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED +import io.novafoundation.nova.feature_wallet_api.domain.model.planksFromAmount +import io.novafoundation.nova.feature_wallet_api.domain.model.regularTransferableBalance +import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory +import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance +import io.novafoundation.nova.feature_wallet_api.domain.validation.validAddress +import io.novafoundation.nova.feature_wallet_api.domain.validation.validate +import io.novafoundation.nova.runtime.ext.accountIdOf +import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset +import java.math.BigDecimal + +typealias AddStakingProxyValidationSystem = ValidationSystem +typealias AddStakingProxyValidationSystemBuilder = ValidationSystemBuilder + +fun AddStakingProxyValidationSystemBuilder.validAddress() = validAddress( + address = { it.proxyAddress }, + chain = { it.chain }, + error = { AddStakingProxyValidationFailure.InvalidAddress(it.chain) } +) + +fun AddStakingProxyValidationSystemBuilder.sufficientBalanceToStayAboveEd( + enoughTotalToStayAboveEDValidationFactory: EnoughTotalToStayAboveEDValidationFactory +) = enoughTotalToStayAboveEDValidationFactory.validate( + chainWithAsset = { ChainWithAsset(it.chain, it.asset.token.configuration) }, + balance = { it.asset.balanceCountedTowardsED() }, + fee = { it.fee }, + error = { payload, _ -> + AddStakingProxyValidationFailure.NotEnoughToStayAboveED( + asset = payload.asset.token.configuration + ) + } +) + +fun AddStakingProxyValidationSystemBuilder.sufficientBalanceToPayFee() = + sufficientBalance( + available = { it.asset.transferable }, + amount = { BigDecimal.ZERO }, + fee = { it.fee }, + error = { context -> + AddStakingProxyValidationFailure.NotEnoughToPayFee( + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.maxUsable, + fee = context.fee + ) + } + ) + +fun AddStakingProxyValidationSystemBuilder.maximumProxies( + proxyRepository: GetProxyRepository +) = maximumProxiesNotReached( + chain = { it.chain }, + accountId = { it.proxiedAccountId }, + proxiesQuantity = { it.currentQuantity + 1 }, + error = { payload, max -> + AddStakingProxyValidationFailure.MaximumProxiesReached( + chain = payload.chain, + max = max + ) + }, + proxyRepository = proxyRepository +) + +fun AddStakingProxyValidationSystemBuilder.enoughBalanceToPayDepositAndFee() = sufficientBalance( + fee = { it.fee }, + amount = { it.asset.token.configuration.amountFromPlanks(it.deltaDeposit) }, + available = { it.asset.token.amountFromPlanks(it.asset.regularTransferableBalance()) }, + error = { + val chainAsset = it.payload.asset.token.configuration + AddStakingProxyValidationFailure.NotEnoughBalanceToReserveDeposit( + chainAsset = chainAsset, + maxUsable = chainAsset.planksFromAmount(it.maxUsable), + deposit = it.payload.deltaDeposit + ) + } +) + +fun AddStakingProxyValidationSystemBuilder.stakingTypeIsNotDuplication( + proxyRepository: GetProxyRepository +) = proxyIsNotDuplicationForAccount( + chain = { it.chain }, + proxiedAccountId = { it.proxiedAccountId }, + proxyAccountId = { it.chain.accountIdOf(it.proxyAddress) }, + proxyType = { ProxyType.Staking }, + error = { payload -> AddStakingProxyValidationFailure.AlreadyDelegated(payload.proxyAddress) }, + proxyRepository = proxyRepository +) + +fun AddStakingProxyValidationSystemBuilder.notSelfAccount( + accountRepository: AccountRepository +) = notSelfAccount( + chainProvider = { it.chain }, + accountIdProvider = { it.chain.accountIdOf(it.proxyAddress) }, + failure = { AddStakingProxyValidationFailure.SelfDelegation }, + accountRepository = accountRepository +) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/Declarations.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/Declarations.kt new file mode 100644 index 0000000000..7da3fac0d2 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/Declarations.kt @@ -0,0 +1,41 @@ +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove + +import io.novafoundation.nova.common.validation.ValidationSystem +import io.novafoundation.nova.common.validation.ValidationSystemBuilder +import io.novafoundation.nova.feature_wallet_api.domain.model.balanceCountedTowardsED +import io.novafoundation.nova.feature_wallet_api.domain.validation.EnoughTotalToStayAboveEDValidationFactory +import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance +import io.novafoundation.nova.feature_wallet_api.domain.validation.validate +import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset +import java.math.BigDecimal + +typealias RemoveStakingProxyValidationSystem = ValidationSystem +typealias RemoveStakingProxyValidationSystemBuilder = ValidationSystemBuilder + +fun RemoveStakingProxyValidationSystemBuilder.sufficientBalanceToStayAboveEd( + enoughTotalToStayAboveEDValidationFactory: EnoughTotalToStayAboveEDValidationFactory +) = enoughTotalToStayAboveEDValidationFactory.validate( + chainWithAsset = { ChainWithAsset(it.chain, it.asset.token.configuration) }, + balance = { it.asset.balanceCountedTowardsED() }, + fee = { it.fee }, + error = { payload, _ -> + RemoveStakingProxyValidationFailure.NotEnoughToStayAboveED( + asset = payload.asset.token.configuration + ) + } +) + +fun RemoveStakingProxyValidationSystemBuilder.sufficientBalanceToPayFee() { + return sufficientBalance( + available = { it.asset.transferable }, + amount = { BigDecimal.ZERO }, + fee = { it.fee }, + error = { context -> + RemoveStakingProxyValidationFailure.NotEnoughToPayFee( + chainAsset = context.payload.asset.token.configuration, + maxUsable = context.maxUsable, + fee = context.fee + ) + } + ) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/RemoveStakingProxyValidationFailure.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/RemoveStakingProxyValidationFailure.kt new file mode 100644 index 0000000000..0a80ac80bb --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/RemoveStakingProxyValidationFailure.kt @@ -0,0 +1,17 @@ +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove + +import io.novafoundation.nova.feature_wallet_api.domain.validation.InsufficientBalanceToStayAboveEDError +import io.novafoundation.nova.feature_wallet_api.domain.validation.NotEnoughToPayFeesError +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import java.math.BigDecimal + +sealed interface RemoveStakingProxyValidationFailure { + + class NotEnoughToPayFee( + override val chainAsset: Chain.Asset, + override val maxUsable: BigDecimal, + override val fee: BigDecimal + ) : RemoveStakingProxyValidationFailure, NotEnoughToPayFeesError + + class NotEnoughToStayAboveED(override val asset: Chain.Asset) : RemoveStakingProxyValidationFailure, InsufficientBalanceToStayAboveEDError +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/RemoveStakingProxyValidationPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/RemoveStakingProxyValidationPayload.kt new file mode 100644 index 0000000000..1909ea1ea2 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/delegation/proxy/remove/RemoveStakingProxyValidationPayload.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove + +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 jp.co.soramitsu.fearless_utils.runtime.AccountId + +class RemoveStakingProxyValidationPayload( + val chain: Chain, + val asset: Asset, + val proxiedAccountId: AccountId, + val proxyAddress: String, + val fee: DecimalFee +) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/Reused.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/Reused.kt index 4646c0311d..28fbea3e7b 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/Reused.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/Reused.kt @@ -1,10 +1,13 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.main import io.novafoundation.nova.feature_staking_impl.domain.validations.AccountRequiredValidation +import io.novafoundation.nova.feature_staking_impl.domain.validations.StashOnlyIsAllowedValidation import io.novafoundation.nova.feature_staking_impl.domain.validations.UnbondingRequestsLimitValidation -const val BALANCE_REQUIRED_STASH = "MainStakingAccountRequiredValidation.Stash" +const val BALANCE_CONTROLLER_IS_NOT_ALLOWED = "ControllerAccountIsNotAllowedValidation" +const val BALANCE_REQUIRED_STASH_META_ACCOUNT = "MainStakingAccountRequiredValidation.Stash" const val BALANCE_REQUIRED_CONTROLLER = "MainStakingAccountRequiredValidation.Controller" -typealias MainStakingAccountRequiredValidation = AccountRequiredValidation +typealias ControllerAccountIsNotAllowedValidation = StashOnlyIsAllowedValidation +typealias MainStakingMetaAccountRequiredValidation = AccountRequiredValidation typealias MainStakingUnlockingLimitValidation = UnbondingRequestsLimitValidation diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/StakeActionsValidationFailure.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/StakeActionsValidationFailure.kt index 990baaac19..aec7cd171c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/StakeActionsValidationFailure.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/StakeActionsValidationFailure.kt @@ -1,5 +1,7 @@ package io.novafoundation.nova.feature_staking_impl.domain.validations.main +import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount + sealed class StakeActionsValidationFailure { class UnbondingRequestLimitReached(val limit: Int) : StakeActionsValidationFailure() @@ -7,4 +9,6 @@ sealed class StakeActionsValidationFailure { class ControllerRequired(val controllerAddress: String) : StakeActionsValidationFailure() class StashRequired(val stashAddress: String) : StakeActionsValidationFailure() + + class StashRequiredToManageProxies(val stashAddress: String, val stashMetaAccount: MetaAccount?) : StakeActionsValidationFailure() } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/ValidationSystems.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/ValidationSystems.kt index a5ce0048e5..077adc17c2 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/ValidationSystems.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validations/main/ValidationSystems.kt @@ -11,5 +11,7 @@ const val SYSTEM_MANAGE_PAYOUTS = "ManageStakingPayouts" const val SYSTEM_MANAGE_VALIDATORS = "ManageStakingValidators" const val SYSTEM_MANAGE_CONTROLLER = "ManageStakingController" const val SYSTEM_MANAGE_STAKING_REBAG = "ManageStakingRebag" +const val SYSTEM_ADD_PROXY = "AddStakingProxy" +const val SYSTEM_MANAGE_PROXIES = "ManageStakingProxies" typealias StakeActionsValidationSystem = ValidationSystem 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 558a328eac..cb6d0f08e1 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 @@ -14,8 +14,8 @@ import io.novafoundation.nova.feature_staking_impl.data.repository.StakingConsta import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.common.electedExposuresInActiveEra import io.novafoundation.nova.feature_staking_impl.domain.common.isWaiting -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.ChangeStackingValidationSystem -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.controllerAccountAccess +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.ChangeStackingValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.controllerAccountAccess import io.novafoundation.nova.feature_staking_impl.domain.validators.ValidatorProvider import io.novafoundation.nova.feature_staking_impl.domain.validators.ValidatorSource import io.novafoundation.nova.feature_staking_impl.domain.validators.getValidators diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/ParachainStakingRouter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/ParachainStakingRouter.kt index 2971f2d1e4..0692ecbf12 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/ParachainStakingRouter.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/ParachainStakingRouter.kt @@ -37,6 +37,8 @@ interface ParachainStakingRouter { fun openSetupYieldBoost() fun openConfirmYieldBoost(payload: YieldBoostConfirmPayload) + + fun openAddStakingProxy() } fun ParachainStakingRouter.openStartStaking(flowMode: StartParachainStakingMode) = openStartStaking(StartParachainStakingPayload(flowMode)) 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 7682f0bc93..04bea06c6d 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 @@ -5,7 +5,9 @@ import io.novafoundation.nova.feature_staking_impl.presentation.payouts.confirm. import io.novafoundation.nova.feature_staking_impl.presentation.payouts.model.PendingPayoutParcelable import io.novafoundation.nova.feature_staking_impl.presentation.pools.common.SelectingPoolPayload import io.novafoundation.nova.feature_staking_impl.presentation.staking.bond.confirm.ConfirmBondMorePayload -import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm.ConfirmSetControllerPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyPayload import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.model.StakingStoryModel import io.novafoundation.nova.feature_staking_impl.presentation.staking.rebond.confirm.ConfirmRebondPayload import io.novafoundation.nova.feature_staking_impl.presentation.staking.rewardDestination.confirm.parcel.ConfirmRewardDestinationPayload @@ -93,4 +95,12 @@ interface StakingRouter { fun finishSetupPoolFlow() fun finishRedeemFlow(redeemConsequences: RedeemConsequences) + + fun openAddStakingProxy() + + fun openConfirmAddStakingProxy(payload: ConfirmAddStakingProxyPayload) + + fun openStakingProxyList() + + fun openConfirmRemoveStakingProxy(payload: ConfirmRemoveStakingProxyPayload) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/ConfirmSetControllerFragment.kt similarity index 98% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerFragment.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/ConfirmSetControllerFragment.kt index 349c468ae8..cbccf7d087 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerFragment.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/ConfirmSetControllerFragment.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm import android.os.Bundle import android.view.LayoutInflater 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/delegation/controller/confirm/ConfirmSetControllerPayload.kt similarity index 91% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerPayload.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/ConfirmSetControllerPayload.kt index 385b742f66..12a49b89eb 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/delegation/controller/confirm/ConfirmSetControllerPayload.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm import android.os.Parcelable import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel 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/delegation/controller/confirm/ConfirmSetControllerViewModel.kt similarity index 94% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/ConfirmSetControllerViewModel.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/ConfirmSetControllerViewModel.kt index 655ac315ba..c88f923a09 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/delegation/controller/confirm/ConfirmSetControllerViewModel.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm import io.novafoundation.nova.common.address.AddressIconGenerator import io.novafoundation.nova.common.base.BaseViewModel @@ -13,11 +13,11 @@ 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_impl.R import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor -import io.novafoundation.nova.feature_staking_impl.domain.staking.controller.ControllerInteractor -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationPayload -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.controller.ControllerInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationPayload +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationSystem 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_staking_impl.presentation.staking.delegation.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 diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/di/ConfirmSetControllerComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/di/ConfirmSetControllerComponent.kt similarity index 81% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/di/ConfirmSetControllerComponent.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/di/ConfirmSetControllerComponent.kt index 9696d4817c..2ab54de1fd 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/di/ConfirmSetControllerComponent.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/di/ConfirmSetControllerComponent.kt @@ -1,11 +1,11 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm.di +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.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_staking_impl.presentation.staking.controller.confirm.ConfirmSetControllerFragment -import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm.ConfirmSetControllerPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerPayload @Subcomponent( modules = [ diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/di/ConfirmSetControllerModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/di/ConfirmSetControllerModule.kt similarity index 91% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/di/ConfirmSetControllerModule.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/di/ConfirmSetControllerModule.kt index 95ea9195c1..71fac83dfa 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/confirm/di/ConfirmSetControllerModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/confirm/di/ConfirmSetControllerModule.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.confirm.di +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.di import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModel @@ -15,11 +15,11 @@ 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_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor -import io.novafoundation.nova.feature_staking_impl.domain.staking.controller.ControllerInteractor -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.controller.ControllerInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationSystem 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_staking_impl.presentation.staking.controller.confirm.ConfirmSetControllerViewModel +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerViewModel @Module(includes = [ViewModelModule::class]) class ConfirmSetControllerModule { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/SetControllerFragment.kt similarity index 99% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerFragment.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/SetControllerFragment.kt index b5d90ab20b..e41e7dfd03 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerFragment.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/SetControllerFragment.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.set +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.set import android.os.Bundle import android.view.LayoutInflater 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/delegation/controller/set/SetControllerViewModel.kt similarity index 97% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/SetControllerViewModel.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/SetControllerViewModel.kt index 2e3d7bc762..d8d64f4e1a 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/delegation/controller/set/SetControllerViewModel.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.set +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.set import androidx.lifecycle.viewModelScope import io.novafoundation.nova.common.address.AddressIconGenerator @@ -24,16 +24,17 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.icon.cre import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_staking_api.domain.model.StakingAccount import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState +import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.accountIsStash import io.novafoundation.nova.feature_staking_impl.R import io.novafoundation.nova.feature_staking_impl.data.repository.ControllersDeprecationStage import io.novafoundation.nova.feature_staking_impl.data.repository.ControllersDeprecationStage.DEPRECATED import io.novafoundation.nova.feature_staking_impl.data.repository.ControllersDeprecationStage.NORMAL import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor -import io.novafoundation.nova.feature_staking_impl.domain.staking.controller.ControllerInteractor -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationPayload -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.controller.ControllerInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationPayload +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationSystem 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_staking_impl.presentation.staking.delegation.controller.confirm.ConfirmSetControllerPayload 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 @@ -252,7 +253,7 @@ class SetControllerViewModel( } private fun StakingState.Stash.isUsingCorrectAccountToChangeController(): Boolean { - return accountId.contentEquals(stashId) + return accountIsStash() } private fun StakingState.Stash.isUsingWrongAccountToChangeController(): Boolean { @@ -271,6 +272,7 @@ class SetControllerViewModel( imageRes = R.drawable.shield, bannerBackgroundRes = R.drawable.ic_banner_grey_gradient ) + DEPRECATED -> AdvertisementCardModel( title = resourceManager.getString(R.string.staking_set_controller_deprecated_title), subtitle = resourceManager.getString(R.string.staking_set_controller_deprecated_subtitle), diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/ValidationFailureMessage.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/ValidationFailureMessage.kt similarity index 92% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/ValidationFailureMessage.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/ValidationFailureMessage.kt index 5fd0185a8d..3b0c385597 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/ValidationFailureMessage.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/ValidationFailureMessage.kt @@ -1,9 +1,9 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.set +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.set import io.novafoundation.nova.common.base.TitleAndMessage import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_staking_impl.R -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationFailure +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationFailure fun bondSetControllerValidationFailure( reason: SetControllerValidationFailure, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/di/SetControllerComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/di/SetControllerComponent.kt similarity index 86% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/di/SetControllerComponent.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/di/SetControllerComponent.kt index 8dc2a42980..39989026db 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/di/SetControllerComponent.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/di/SetControllerComponent.kt @@ -1,10 +1,10 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.set.di +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.set.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_staking_impl.presentation.staking.controller.set.SetControllerFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.set.SetControllerFragment @Subcomponent( modules = [ diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/di/SetControllerModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/di/SetControllerModule.kt similarity index 94% rename from feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/di/SetControllerModule.kt rename to feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/di/SetControllerModule.kt index c2ad946915..f8e93966d3 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/controller/set/di/SetControllerModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/controller/set/di/SetControllerModule.kt @@ -1,4 +1,4 @@ -package io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.set.di +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.set.di import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModel @@ -17,10 +17,10 @@ import io.novafoundation.nova.feature_account_api.presenatation.account.AddressD import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions import io.novafoundation.nova.feature_staking_impl.data.StakingSharedState import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor -import io.novafoundation.nova.feature_staking_impl.domain.staking.controller.ControllerInteractor -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.SetControllerValidationSystem +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.controller.ControllerInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.SetControllerValidationSystem import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter -import io.novafoundation.nova.feature_staking_impl.presentation.staking.controller.set.SetControllerViewModel +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.controller.set.SetControllerViewModel import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin @Module(includes = [ViewModelModule::class]) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyFragment.kt new file mode 100644 index 0000000000..ef02c9724e --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyFragment.kt @@ -0,0 +1,87 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import io.novafoundation.nova.common.base.BaseFragment +import io.novafoundation.nova.common.di.FeatureUtils +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.feature_account_api.presenatation.account.wallet.showWallet +import io.novafoundation.nova.feature_account_api.presenatation.actions.setupExternalActions +import io.novafoundation.nova.feature_account_api.view.showAddress +import io.novafoundation.nova.feature_account_api.view.showChain +import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi +import io.novafoundation.nova.feature_staking_impl.R +import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureComponent +import io.novafoundation.nova.feature_wallet_api.presentation.view.showAmount +import kotlinx.android.synthetic.main.fragment_confirm_add_staking_proxy.confirmAddStakingProxyButton +import kotlinx.android.synthetic.main.fragment_confirm_add_staking_proxy.confirmAddStakingProxyDelegationAccount +import kotlinx.android.synthetic.main.fragment_confirm_add_staking_proxy.confirmAddStakingProxyDeposit +import kotlinx.android.synthetic.main.fragment_confirm_add_staking_proxy.confirmAddStakingProxyNetwork +import kotlinx.android.synthetic.main.fragment_confirm_add_staking_proxy.confirmAddStakingProxyNetworkFee +import kotlinx.android.synthetic.main.fragment_confirm_add_staking_proxy.confirmAddStakingProxyProxiedAccount +import kotlinx.android.synthetic.main.fragment_confirm_add_staking_proxy.confirmAddStakingProxyToolbar +import kotlinx.android.synthetic.main.fragment_confirm_add_staking_proxy.confirmAddStakingProxyWallet + +class ConfirmAddStakingProxyFragment : BaseFragment() { + companion object { + + private const val PAYLOAD_KEY = "PAYLOAD_KEY" + + fun getBundle(payload: ConfirmAddStakingProxyPayload) = Bundle().apply { + putParcelable(PAYLOAD_KEY, payload) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_confirm_add_staking_proxy, container, false) + } + + override fun initViews() { + confirmAddStakingProxyToolbar.applyStatusBarInsets() + + confirmAddStakingProxyToolbar.setHomeButtonListener { viewModel.back() } + + confirmAddStakingProxyButton.setOnClickListener { viewModel.confirmClicked() } + confirmAddStakingProxyButton.prepareForProgress(viewLifecycleOwner) + + confirmAddStakingProxyProxiedAccount.setOnClickListener { viewModel.proxiedAccountClicked() } + confirmAddStakingProxyDeposit.setOnClickListener { viewModel.depositClicked() } + confirmAddStakingProxyDelegationAccount.setOnClickListener { viewModel.proxyAccountClicked() } + } + + override fun inject() { + val payload = argument(PAYLOAD_KEY) + + FeatureUtils.getFeature( + requireContext(), + StakingFeatureApi::class.java + ) + .confirmAddStakingProxyFactory() + .create(this, payload) + .inject(this) + } + + override fun subscribe(viewModel: ConfirmAddStakingProxyViewModel) { + observeValidations(viewModel) + setupExternalActions(viewModel) + observeDescription(viewModel) + + viewModel.chainModel.observe { confirmAddStakingProxyNetwork.showChain(it) } + viewModel.walletUiFlow.observe { confirmAddStakingProxyWallet.showWallet(it) } + viewModel.proxiedAccountModel.observe { confirmAddStakingProxyProxiedAccount.showAddress(it) } + viewModel.proxyDeposit.observe { confirmAddStakingProxyDeposit.showAmount(it) } + viewModel.feeModelFlow.observe { confirmAddStakingProxyNetworkFee.showAmount(it) } + viewModel.proxyAccountModel.observe { confirmAddStakingProxyDelegationAccount.showAddress(it) } + + viewModel.validationProgressFlow.observe(confirmAddStakingProxyButton::setProgress) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyPayload.kt new file mode 100644 index 0000000000..fc31648a8e --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyPayload.kt @@ -0,0 +1,14 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm + +import android.os.Parcelable +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeParcelModel +import kotlinx.android.parcel.Parcelize + +@Parcelize +class ConfirmAddStakingProxyPayload( + val fee: FeeParcelModel, + val proxyAddress: String, + val deltaDeposit: Balance, + val currentQuantity: Int +) : Parcelable diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyViewModel.kt new file mode 100644 index 0000000000..99359b1b76 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/ConfirmAddStakingProxyViewModel.kt @@ -0,0 +1,159 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm + +import io.novafoundation.nova.common.address.AddressIconGenerator +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.mixin.api.Validatable +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.common.validation.ValidationExecutor +import io.novafoundation.nova.common.validation.progressConsumer +import io.novafoundation.nova.common.view.bottomSheet.description.DescriptionBottomSheetLauncher +import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +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.icon.createAccountAddressModel +import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.AddStakingProxyInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationPayload +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationSystem +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.common.launchProxyDepositDescription +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.common.mapAddStakingProxyValidationFailureToUi +import io.novafoundation.nova.feature_wallet_api.domain.ArbitraryAssetUseCase +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.ext.accountIdOf +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState +import io.novafoundation.nova.runtime.state.chain +import io.novafoundation.nova.runtime.state.selectedAssetFlow +import io.novafoundation.nova.runtime.state.selectedChainFlow +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class ConfirmAddStakingProxyViewModel( + private val router: StakingRouter, + private val addressIconGenerator: AddressIconGenerator, + private val payload: ConfirmAddStakingProxyPayload, + private val accountRepository: AccountRepository, + private val resourceManager: ResourceManager, + private val externalActions: ExternalActions.Presentation, + private val validationExecutor: ValidationExecutor, + private val selectedAssetState: AnySelectedAssetOptionSharedState, + private val assetUseCase: ArbitraryAssetUseCase, + private val addStakingProxyValidationSystem: AddStakingProxyValidationSystem, + private val addStakingProxyInteractor: AddStakingProxyInteractor, + private val walletUiUseCase: WalletUiUseCase, + private val descriptionBottomSheetLauncher: DescriptionBottomSheetLauncher, +) : BaseViewModel(), + DescriptionBottomSheetLauncher by descriptionBottomSheetLauncher, + Validatable by validationExecutor, + ExternalActions by externalActions { + + private val selectedMetaAccountFlow = accountRepository.selectedMetaAccountFlow() + .shareInBackground() + + private val chainFlow = selectedAssetState.selectedChainFlow() + .shareInBackground() + + @OptIn(ExperimentalCoroutinesApi::class) + private val assetFlow = selectedAssetState.selectedAssetFlow() + .flatMapLatest { assetUseCase.assetFlow(it) } + .shareInBackground() + + private val decimalFeeFlow = flowOf { mapFeeFromParcel(payload.fee) } + .shareInBackground() + + val chainModel = chainFlow.map { chain -> + mapChainToUi(chain) + } + + val walletUiFlow = selectedMetaAccountFlow.map { walletUiUseCase.walletUiFor(it) } + + val proxiedAccountModel = combine(selectedMetaAccountFlow, chainFlow) { metaAccount, chain -> + val address = metaAccount.requireAddressIn(chain) + + generateAccountAddressModel(chain, address) + } + + val proxyDeposit = assetFlow.map { asset -> + mapAmountToAmountModel(payload.deltaDeposit, asset) + } + + val feeModelFlow = combine(assetFlow, decimalFeeFlow) { asset, decimalFee -> + mapAmountToAmountModel(decimalFee.networkFee.amount, asset) + } + + val proxyAccountModel = chainFlow.map { chain -> + generateAccountAddressModel(chain, payload.proxyAddress) + } + + val validationProgressFlow = MutableStateFlow(false) + + fun back() { + router.back() + } + + fun confirmClicked() = launch { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = selectedAssetState.chain() + val validationPayload = AddStakingProxyValidationPayload( + chain = chain, + asset = assetFlow.first(), + proxyAddress = payload.proxyAddress, + proxiedAccountId = metaAccount.requireAccountIdIn(chain), + fee = decimalFeeFlow.first(), + deltaDeposit = payload.deltaDeposit, + currentQuantity = payload.currentQuantity + ) + + validationExecutor.requireValid( + validationSystem = addStakingProxyValidationSystem, + payload = validationPayload, + validationFailureTransformer = { mapAddStakingProxyValidationFailureToUi(resourceManager, it) }, + progressConsumer = validationProgressFlow.progressConsumer() + ) { + sendTransaction(it.chain, it.proxiedAccountId, it.chain.accountIdOf(it.proxyAddress)) + } + } + + private fun sendTransaction(chain: Chain, proxiedAccount: AccountId, proxyAccount: AccountId) = launch { + val result = addStakingProxyInteractor.addProxy(chain, proxiedAccount, proxyAccount) + + validationProgressFlow.value = false + + result.onSuccess { router.returnToStakingMain() } + .onFailure(::showError) + } + + private suspend fun generateAccountAddressModel(chain: Chain, address: String) = addressIconGenerator.createAccountAddressModel( + chain = chain, + address = address, + ) + + fun proxiedAccountClicked() { + launch { + showExternalActions(proxiedAccountModel.first().address) + } + } + + fun depositClicked() { + descriptionBottomSheetLauncher.launchProxyDepositDescription() + } + + fun proxyAccountClicked() { + showExternalActions(payload.proxyAddress) + } + + private fun showExternalActions(address: String) = launch { + externalActions.showExternalActions(ExternalActions.Type.Address(address), selectedAssetState.chain()) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/di/ConfirmAddStakingProxyComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/di/ConfirmAddStakingProxyComponent.kt new file mode 100644 index 0000000000..54b085126b --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/di/ConfirmAddStakingProxyComponent.kt @@ -0,0 +1,27 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.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_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyPayload + +@Subcomponent( + modules = [ + ConfirmAddStakingProxyModule::class + ] +) +@ScreenScope +interface ConfirmAddStakingProxyComponent { + + @Subcomponent.Factory + interface Factory { + fun create( + @BindsInstance fragment: Fragment, + @BindsInstance payload: ConfirmAddStakingProxyPayload, + ): ConfirmAddStakingProxyComponent + } + + fun inject(fragment: ConfirmAddStakingProxyFragment) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/di/ConfirmAddStakingProxyModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/di/ConfirmAddStakingProxyModule.kt new file mode 100644 index 0000000000..cb22bfcb2a --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/confirm/di/ConfirmAddStakingProxyModule.kt @@ -0,0 +1,70 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.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.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.validation.ValidationExecutor +import io.novafoundation.nova.common.view.bottomSheet.description.DescriptionBottomSheetLauncher +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.AddStakingProxyInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationSystem +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyViewModel +import io.novafoundation.nova.feature_wallet_api.domain.ArbitraryAssetUseCase +import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState + +@Module(includes = [ViewModelModule::class]) +class ConfirmAddStakingProxyModule { + @Provides + @IntoMap + @ViewModelKey(ConfirmAddStakingProxyViewModel::class) + fun provideViewModule( + router: StakingRouter, + addressIconGenerator: AddressIconGenerator, + payload: ConfirmAddStakingProxyPayload, + accountRepository: AccountRepository, + resourceManager: ResourceManager, + externalActions: ExternalActions.Presentation, + validationExecutor: ValidationExecutor, + selectedAssetState: AnySelectedAssetOptionSharedState, + assetUseCase: ArbitraryAssetUseCase, + addStakingProxyValidationSystem: AddStakingProxyValidationSystem, + addStakingProxyRepository: AddStakingProxyInteractor, + walletUiUseCase: WalletUiUseCase, + descriptionBottomSheetLauncher: DescriptionBottomSheetLauncher, + ): ViewModel { + return ConfirmAddStakingProxyViewModel( + router = router, + addressIconGenerator = addressIconGenerator, + payload = payload, + accountRepository = accountRepository, + resourceManager = resourceManager, + externalActions = externalActions, + validationExecutor = validationExecutor, + selectedAssetState = selectedAssetState, + assetUseCase = assetUseCase, + addStakingProxyValidationSystem = addStakingProxyValidationSystem, + addStakingProxyInteractor = addStakingProxyRepository, + walletUiUseCase = walletUiUseCase, + descriptionBottomSheetLauncher = descriptionBottomSheetLauncher + ) + } + + @Provides + fun provideViewModelCreator( + fragment: Fragment, + viewModelFactory: ViewModelProvider.Factory + ): ConfirmAddStakingProxyViewModel { + return ViewModelProvider(fragment, viewModelFactory).get(ConfirmAddStakingProxyViewModel::class.java) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/AddStakingProxyFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/AddStakingProxyFragment.kt new file mode 100644 index 0000000000..f9b8c6c86d --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/AddStakingProxyFragment.kt @@ -0,0 +1,82 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.set + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import io.novafoundation.nova.common.base.BaseFragment +import io.novafoundation.nova.common.di.FeatureUtils +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.setState +import io.novafoundation.nova.feature_account_api.presenatation.actions.setupExternalActions +import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.setupAddressInput +import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.setupExternalAccounts +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.setupYourWalletsBtn +import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi +import io.novafoundation.nova.feature_staking_impl.R +import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureComponent +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.setupFeeLoading +import io.novafoundation.nova.feature_wallet_api.presentation.view.showAmount +import kotlinx.android.synthetic.main.fragment_add_staking_proxy.addProxyToolbar +import kotlinx.android.synthetic.main.fragment_add_staking_proxy.setStakingProxyAddress +import kotlinx.android.synthetic.main.fragment_add_staking_proxy.addStakingProxyButton +import kotlinx.android.synthetic.main.fragment_add_staking_proxy.setStakingProxyContainer +import kotlinx.android.synthetic.main.fragment_add_staking_proxy.addStakingProxyDeposit +import kotlinx.android.synthetic.main.fragment_add_staking_proxy.addStakingProxyFee +import kotlinx.android.synthetic.main.fragment_add_staking_proxy.addStakingProxyTitle +import kotlinx.android.synthetic.main.fragment_add_staking_proxy.addStakingProxySelectWallet + +class AddStakingProxyFragment : BaseFragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.fragment_add_staking_proxy, container, false) + } + + override fun initViews() { + setStakingProxyContainer.applyStatusBarInsets(consume = false) + addProxyToolbar.setHomeButtonListener { viewModel.backClicked() } + addStakingProxyButton.prepareForProgress(this) + + addStakingProxySelectWallet.setOnClickListener { viewModel.selectAuthorityWallet() } + addStakingProxyButton.setOnClickListener { viewModel.onConfirmClick() } + addStakingProxyDeposit.setOnClickListener { viewModel.showProxyDepositDescription() } + } + + override fun inject() { + FeatureUtils.getFeature( + requireContext(), + StakingFeatureApi::class.java + ) + .setStakingProxyFactory() + .create(this) + .inject(this) + } + + override fun subscribe(viewModel: AddStakingProxyViewModel) { + setupExternalActions(viewModel) + observeValidations(viewModel) + observeDescription(viewModel) + + setupAddressInput(viewModel.addressInputMixin, setStakingProxyAddress) + setupExternalAccounts(viewModel.addressInputMixin, setStakingProxyAddress) + setupYourWalletsBtn(addStakingProxySelectWallet, viewModel.selectAddressMixin) + + viewModel.titleFlow.observe { + addStakingProxyTitle.text = it + } + + viewModel.proxyDepositModel.observe { + addStakingProxyDeposit.showAmount(it) + } + + setupFeeLoading(viewModel.feeMixin, addStakingProxyFee) + + viewModel.continueButtonState.observe(addStakingProxyButton::setState) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/AddStakingProxyViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/AddStakingProxyViewModel.kt new file mode 100644 index 0000000000..5d6bb08f55 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/AddStakingProxyViewModel.kt @@ -0,0 +1,254 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.set + +import io.novafoundation.nova.common.address.intoKey +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.mixin.api.Validatable +import io.novafoundation.nova.common.presentation.DescriptiveButtonState +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.flowOf +import io.novafoundation.nova.common.validation.ValidationExecutor +import io.novafoundation.nova.common.validation.progressConsumer +import io.novafoundation.nova.common.view.bottomSheet.description.DescriptionBottomSheetLauncher +import io.novafoundation.nova.feature_account_api.domain.filter.selectAddress.SelectAddressAccountFilter +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.accountIdIn +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressRequester +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.AddStakingProxyInteractor +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.validations.delegation.proxy.add.AddStakingProxyValidationPayload +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationSystem +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.common.launchProxyDepositDescription +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.common.mapAddStakingProxyValidationFailureToUi +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.confirm.ConfirmAddStakingProxyPayload +import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance +import io.novafoundation.nova.feature_wallet_api.domain.ArbitraryAssetUseCase +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.create +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.mapFeeToParcel +import io.novafoundation.nova.feature_wallet_api.presentation.model.AmountModel +import io.novafoundation.nova.feature_wallet_api.presentation.model.mapAmountToAmountModel +import io.novafoundation.nova.runtime.ext.commissionAsset +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState +import io.novafoundation.nova.runtime.state.assetWithChain +import io.novafoundation.nova.runtime.state.chain +import io.novafoundation.nova.runtime.state.selectedAssetFlow +import io.novafoundation.nova.runtime.state.selectedChainFlow +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +class AddStakingProxyViewModel( + addressInputMixinFactory: AddressInputMixinFactory, + feeLoaderMixinFactory: FeeLoaderMixin.Factory, + private val selectedAssetState: AnySelectedAssetOptionSharedState, + private val externalActions: ExternalActions.Presentation, + private val interactor: StakingInteractor, + private val accountRepository: AccountRepository, + private val assetUseCase: ArbitraryAssetUseCase, + private val resourceManager: ResourceManager, + private val addStakingProxyInteractor: AddStakingProxyInteractor, + private val validationExecutor: ValidationExecutor, + private val addStakingProxyValidationSystem: AddStakingProxyValidationSystem, + private val descriptionBottomSheetLauncher: DescriptionBottomSheetLauncher, + private val metaAccountGroupingInteractor: MetaAccountGroupingInteractor, + private val stakingRouter: StakingRouter, + private val selectAddressMixinFactory: SelectAddressMixin.Factory, + private val getProxyRepository: GetProxyRepository +) : BaseViewModel(), + DescriptionBottomSheetLauncher by descriptionBottomSheetLauncher, + ExternalActions by externalActions, + Validatable by validationExecutor { + + @OptIn(ExperimentalCoroutinesApi::class) + private val commissionAssetFlow = selectedAssetState.selectedChainFlow() + .flatMapLatest { assetUseCase.assetFlow(it.id, it.commissionAsset.id) } + .shareInBackground() + + @OptIn(ExperimentalCoroutinesApi::class) + private val selectedAssetFlow = selectedAssetState.selectedAssetFlow() + .flatMapLatest { assetUseCase.assetFlow(it) } + .shareInBackground() + + private val selectAddressPayloadFlow = flowOf { + val selectedMetaAccount = accountRepository.getSelectedMetaAccount() + val chain = selectedAssetState.chain() + val filter = metaAccountsFilter(chain, selectedMetaAccount.requireAccountIdIn(chain)) + SelectAddressMixin.Payload(chain, filter) + } + + val selectAddressMixin = selectAddressMixinFactory.create(this, selectAddressPayloadFlow, ::onAddressSelect) + + val titleFlow = selectedAssetFlow.map { + resourceManager.getString(R.string.fragment_set_staking_proxy_title, it.token.configuration.symbol) + } + .shareInBackground() + + val addressInputMixin = with(addressInputMixinFactory) { + val inputSpec = singleChainInputSpec(selectedAssetState.selectedChainFlow()) + + create( + inputSpecProvider = inputSpec, + myselfBehaviorProvider = noMyself(), + accountIdentifierProvider = web3nIdentifiers( + destinationChainFlow = selectedAssetState.assetWithChain, + inputSpecProvider = inputSpec, + coroutineScope = this@AddStakingProxyViewModel, + ), + errorDisplayer = this@AddStakingProxyViewModel::showError, + showAccountEvent = this@AddStakingProxyViewModel::showAccountDetails, + coroutineScope = this@AddStakingProxyViewModel, + ) + } + + val isSelectAddressAvailable = flowOf { + val selectedMetaAccount = accountRepository.getSelectedMetaAccount() + val chain = selectedAssetState.chain() + val filter = metaAccountsFilter(chain, selectedMetaAccount.requireAccountIdIn(chain)) + metaAccountGroupingInteractor.hasAvailableMetaAccountsForChain(selectedAssetState.chainId(), filter) + } + .shareInBackground() + + private val proxyDepositDelta: Flow = flowOf { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = selectedAssetState.chain() + val accountId = metaAccount.requireAccountIdIn(chain) + addStakingProxyInteractor.calculateDeltaDepositForAddProxy(chain, accountId) + } + .shareInBackground() + + val proxyDepositModel: Flow = combine(proxyDepositDelta, selectedAssetFlow) { depositDelta, asset -> + mapAmountToAmountModel(depositDelta, asset) + } + .shareInBackground() + + val feeMixin: FeeLoaderMixin.Presentation = feeLoaderMixinFactory.create(commissionAssetFlow) + + private val _validationProgressFlow = MutableStateFlow(false) + + val continueButtonState = combine( + addressInputMixin.inputFlow, + _validationProgressFlow + ) { addressInput, validationInProgress -> + when { + validationInProgress -> DescriptiveButtonState.Loading + + addressInput.isEmpty() -> DescriptiveButtonState.Disabled(resourceManager.getString(R.string.common_enter_address)) + + else -> DescriptiveButtonState.Enabled(resourceManager.getString(R.string.common_continue)) + } + } + + init { + runFeeUpdate() + } + + fun backClicked() { + stakingRouter.back() + } + + fun showProxyDepositDescription() { + descriptionBottomSheetLauncher.launchProxyDepositDescription() + } + + fun selectAuthorityWallet() { + launch { + val selectedAddress = addressInputMixin.getAddress() + selectAddressMixin.openSelectAddress(selectedAddress) + } + } + + fun onConfirmClick() = launch { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = selectedAssetState.chain() + val proxiedAccountId = metaAccount.requireAccountIdIn(chain) + val validationPayload = AddStakingProxyValidationPayload( + chain = chain, + asset = selectedAssetFlow.first(), + proxyAddress = addressInputMixin.getAddress(), + proxiedAccountId = metaAccount.requireAccountIdIn(chain), + fee = feeMixin.awaitDecimalFee(), + deltaDeposit = proxyDepositDelta.first(), + currentQuantity = getProxyRepository.getProxiesQuantity(chain.id, proxiedAccountId) + ) + + validationExecutor.requireValid( + validationSystem = addStakingProxyValidationSystem, + payload = validationPayload, + validationFailureTransformer = { mapAddStakingProxyValidationFailureToUi(resourceManager, it) }, + progressConsumer = _validationProgressFlow.progressConsumer() + ) { + openConfirmScreen(it) + _validationProgressFlow.value = false + } + } + + private fun openConfirmScreen(validationPayload: AddStakingProxyValidationPayload) { + val screenPayload = validationPayload.run { + ConfirmAddStakingProxyPayload( + fee = mapFeeToParcel(validationPayload.fee), + proxyAddress = proxyAddress, + deltaDeposit = deltaDeposit, + currentQuantity = currentQuantity + ) + } + stakingRouter.openConfirmAddStakingProxy(screenPayload) + } + + private fun onAddressSelect(address: String) { + addressInputMixin.inputFlow.value = address + } + + private fun runFeeUpdate() { + addressInputMixin.inputFlow.onEach { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = selectedAssetState.chain() + + feeMixin.loadFee( + coroutineScope = this, + feeConstructor = { addStakingProxyInteractor.estimateFee(chain, metaAccount.requireAccountIdIn(chain)) }, + onRetryCancelled = {} + ) + }.launchIn(this) + } + + private fun showAccountDetails(address: String) { + launch { + val chain = selectedAssetState.chain() + externalActions.showExternalActions(ExternalActions.Type.Address(address), chain) + } + } + + private suspend fun getMetaAccountsFilterPayload(chain: Chain, accountId: AccountId): SelectAddressRequester.Request.Filter.ExcludeMetaIds { + val filteredMetaAccounts = accountRepository.activeMetaAccounts() + .filter { it.accountIdIn(chain)?.intoKey() == accountId.intoKey() } + .map { it.id } + + return SelectAddressRequester.Request.Filter.ExcludeMetaIds(filteredMetaAccounts) + } + + private suspend fun metaAccountsFilter(chain: Chain, accountId: AccountId): SelectAddressAccountFilter { + val metaAccountsFilterPayload = getMetaAccountsFilterPayload(chain, accountId) + + return SelectAddressAccountFilter.ExcludeMetaAccounts( + metaAccountsFilterPayload.metaIds + ) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/di/AddStakingProxyComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/di/AddStakingProxyComponent.kt new file mode 100644 index 0000000000..46645e8c70 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/di/AddStakingProxyComponent.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.set.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_staking_impl.presentation.staking.delegation.proxy.add.set.AddStakingProxyFragment + +@Subcomponent( + modules = [ + AddStakingProxyModule::class + ] +) +@ScreenScope +interface AddStakingProxyComponent { + + @Subcomponent.Factory + interface Factory { + fun create(@BindsInstance fragment: Fragment): AddStakingProxyComponent + } + + fun inject(fragment: AddStakingProxyFragment) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/di/AddStakingProxyModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/di/AddStakingProxyModule.kt new file mode 100644 index 0000000000..d4edb104b1 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/add/set/di/AddStakingProxyModule.kt @@ -0,0 +1,82 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.set.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.di.viewmodel.ViewModelKey +import io.novafoundation.nova.common.di.viewmodel.ViewModelModule +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.validation.ValidationExecutor +import io.novafoundation.nova.common.view.bottomSheet.description.DescriptionBottomSheetLauncher +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.presenatation.mixin.selectAddress.SelectAddressCommunicator +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_account_api.presenatation.mixin.addressInput.AddressInputMixinFactory +import io.novafoundation.nova.feature_account_api.presenatation.mixin.selectAddress.SelectAddressMixin +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.AddStakingProxyInteractor +import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationSystem +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.add.set.AddStakingProxyViewModel +import io.novafoundation.nova.feature_wallet_api.domain.ArbitraryAssetUseCase +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState + +@Module(includes = [ViewModelModule::class]) +class AddStakingProxyModule { + + @Provides + @IntoMap + @ViewModelKey(AddStakingProxyViewModel::class) + fun provideViewModel( + addressInputMixinFactory: AddressInputMixinFactory, + feeLoaderMixinFactory: FeeLoaderMixin.Factory, + selectedAssetState: AnySelectedAssetOptionSharedState, + externalActions: ExternalActions.Presentation, + interactor: StakingInteractor, + accountRepository: AccountRepository, + assetUseCase: ArbitraryAssetUseCase, + resourceManager: ResourceManager, + selectAddressCommunicator: SelectAddressCommunicator, + addStakingProxyInteractor: AddStakingProxyInteractor, + validationExecutor: ValidationExecutor, + addStakingProxyValidationSystem: AddStakingProxyValidationSystem, + descriptionBottomSheetLauncher: DescriptionBottomSheetLauncher, + metaAccountGroupingInteractor: MetaAccountGroupingInteractor, + stakingRouter: StakingRouter, + selectAddressMixinFactory: SelectAddressMixin.Factory, + getProxyRepository: GetProxyRepository + ): ViewModel { + return AddStakingProxyViewModel( + addressInputMixinFactory = addressInputMixinFactory, + feeLoaderMixinFactory = feeLoaderMixinFactory, + selectedAssetState = selectedAssetState, + externalActions = externalActions, + interactor = interactor, + accountRepository = accountRepository, + assetUseCase = assetUseCase, + resourceManager = resourceManager, + addStakingProxyInteractor = addStakingProxyInteractor, + validationExecutor = validationExecutor, + addStakingProxyValidationSystem = addStakingProxyValidationSystem, + descriptionBottomSheetLauncher = descriptionBottomSheetLauncher, + metaAccountGroupingInteractor = metaAccountGroupingInteractor, + stakingRouter = stakingRouter, + selectAddressMixinFactory = selectAddressMixinFactory, + getProxyRepository = getProxyRepository + ) + } + + @Provides + fun provideViewModelCreator( + fragment: Fragment, + viewModelFactory: ViewModelProvider.Factory + ): AddStakingProxyViewModel { + return ViewModelProvider(fragment, viewModelFactory).get(AddStakingProxyViewModel::class.java) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/AddProxyValidationFailureHandling.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/AddProxyValidationFailureHandling.kt new file mode 100644 index 0000000000..f0b3bbd415 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/AddProxyValidationFailureHandling.kt @@ -0,0 +1,44 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.common + +import io.novafoundation.nova.common.base.TitleAndMessage +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_staking_impl.R +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationFailure +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationFailure.NotEnoughBalanceToReserveDeposit +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationFailure.InvalidAddress +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationFailure.MaximumProxiesReached +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationFailure.NotEnoughToPayFee +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.add.AddStakingProxyValidationFailure.NotEnoughToStayAboveED +import io.novafoundation.nova.feature_wallet_api.domain.validation.handleNotEnoughFeeError +import io.novafoundation.nova.feature_wallet_api.presentation.formatters.formatPlanks +import io.novafoundation.nova.feature_wallet_api.presentation.validation.handleInsufficientBalanceCommission + +fun mapAddStakingProxyValidationFailureToUi( + resourceManager: ResourceManager, + failure: AddStakingProxyValidationFailure, +): TitleAndMessage { + return when (failure) { + is NotEnoughBalanceToReserveDeposit -> resourceManager.getString(R.string.common_error_not_enough_tokens) to + resourceManager.getString( + R.string.staking_not_enough_balance_to_pay_proxy_deposit_message, + failure.deposit.formatPlanks(failure.chainAsset), + failure.maxUsable.formatPlanks(failure.chainAsset) + ) + + is InvalidAddress -> resourceManager.getString(R.string.invalid_proxy_address_title) to + resourceManager.getString(R.string.invalid_proxy_address_message, failure.chain.name) + + AddStakingProxyValidationFailure.SelfDelegation -> resourceManager.getString(R.string.delegation_error_self_delegate_title) to + resourceManager.getString(R.string.delegation_error_self_delegate_message) + + is MaximumProxiesReached -> resourceManager.getString(R.string.add_proxy_maximum_reached_error_title) to + resourceManager.getString(R.string.add_proxy_maximum_reached_error_message, failure.max, failure.chain.name) + + is NotEnoughToPayFee -> handleNotEnoughFeeError(failure, resourceManager) + + is NotEnoughToStayAboveED -> handleInsufficientBalanceCommission(failure, resourceManager) + + is AddStakingProxyValidationFailure.AlreadyDelegated -> resourceManager.getString(R.string.duplicate_proxy_type_title) to + resourceManager.getString(R.string.duplicate_proxy_type_message, failure.address) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/ProxyDepositDescriptionSetupExt.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/ProxyDepositDescriptionSetupExt.kt new file mode 100644 index 0000000000..6802f10e2a --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/ProxyDepositDescriptionSetupExt.kt @@ -0,0 +1,11 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.common + +import io.novafoundation.nova.common.view.bottomSheet.description.DescriptionBottomSheetLauncher +import io.novafoundation.nova.feature_staking_impl.R + +fun DescriptionBottomSheetLauncher.launchProxyDepositDescription() { + launchDescriptionBottomSheet( + titleRes = R.string.common_proxy_deposit, + descriptionRes = R.string.add_proxy_deposit_description_message + ) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/RemoveStakingProxyValidationFailureToUi.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/RemoveStakingProxyValidationFailureToUi.kt new file mode 100644 index 0000000000..490811089b --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/common/RemoveStakingProxyValidationFailureToUi.kt @@ -0,0 +1,20 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.common + +import io.novafoundation.nova.common.base.TitleAndMessage +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.RemoveStakingProxyValidationFailure.NotEnoughToPayFee +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.RemoveStakingProxyValidationFailure.NotEnoughToStayAboveED +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.RemoveStakingProxyValidationFailure +import io.novafoundation.nova.feature_wallet_api.domain.validation.handleNotEnoughFeeError +import io.novafoundation.nova.feature_wallet_api.presentation.validation.handleInsufficientBalanceCommission + +fun mapRemoveStakingProxyValidationFailureToUi( + resourceManager: ResourceManager, + failure: RemoveStakingProxyValidationFailure, +): TitleAndMessage { + return when (failure) { + is NotEnoughToPayFee -> handleNotEnoughFeeError(failure, resourceManager) + + is NotEnoughToStayAboveED -> handleInsufficientBalanceCommission(failure, resourceManager) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListAdapter.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListAdapter.kt new file mode 100644 index 0000000000..ed94707035 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListAdapter.kt @@ -0,0 +1,89 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list + +import android.view.View +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.utils.inflateChild +import io.novafoundation.nova.feature_account_api.presenatation.chain.loadChainIcon +import io.novafoundation.nova.feature_staking_impl.R +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.model.StakingProxyGroupRvItem +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.model.StakingProxyRvItem +import kotlinx.android.synthetic.main.item_proxy.view.itemStakingProxyAccountTitle +import kotlinx.android.synthetic.main.item_proxy.view.itemStakingProxyChainIcon +import kotlinx.android.synthetic.main.item_proxy.view.itemStakingProxyIcon +import kotlinx.android.synthetic.main.item_proxy_group.view.itemProxyGroup + +class StakingProxyListAdapter( + private val handler: Handler, + private val imageLoader: ImageLoader +) : GroupedListAdapter(DiffCallback()) { + + interface Handler { + fun onProxyClick(item: StakingProxyRvItem) + } + + override fun createGroupViewHolder(parent: ViewGroup): GroupedListHolder { + val view = parent.inflateChild(R.layout.item_proxy_group) + return StakingProxyGroupHolder(view) + } + + override fun createChildViewHolder(parent: ViewGroup): GroupedListHolder { + val view = parent.inflateChild(R.layout.item_proxy) + return StakingProxyHolder(handler, imageLoader, view) + } + + override fun bindGroup(holder: GroupedListHolder, group: StakingProxyGroupRvItem) { + require(holder is StakingProxyGroupHolder) + holder.bind(group) + } + + override fun bindChild(holder: GroupedListHolder, child: StakingProxyRvItem) { + require(holder is StakingProxyHolder) + holder.bind(child) + } +} + +private class DiffCallback : BaseGroupedDiffCallback(StakingProxyGroupRvItem::class.java) { + + override fun areGroupItemsTheSame(oldItem: StakingProxyGroupRvItem, newItem: StakingProxyGroupRvItem): Boolean { + return oldItem.text == newItem.text + } + + override fun areGroupContentsTheSame(oldItem: StakingProxyGroupRvItem, newItem: StakingProxyGroupRvItem): Boolean { + return true + } + + override fun areChildItemsTheSame(oldItem: StakingProxyRvItem, newItem: StakingProxyRvItem): Boolean { + return oldItem.accountAddress == newItem.accountAddress + } + + override fun areChildContentsTheSame(oldItem: StakingProxyRvItem, newItem: StakingProxyRvItem): Boolean { + return true + } +} + +class StakingProxyGroupHolder( + containerView: View, +) : GroupedListHolder(containerView) { + + fun bind(item: StakingProxyGroupRvItem) = with(containerView) { + itemProxyGroup.text = item.text + } +} + +class StakingProxyHolder( + private val eventHandler: StakingProxyListAdapter.Handler, + private val imageLoader: ImageLoader, + containerView: View, +) : GroupedListHolder(containerView) { + + fun bind(item: StakingProxyRvItem) = with(containerView) { + setOnClickListener { eventHandler.onProxyClick(item) } + itemStakingProxyIcon.setImageDrawable(item.accountIcon) + itemStakingProxyChainIcon.loadChainIcon(item.chainIconUrl, imageLoader) + itemStakingProxyAccountTitle.text = item.accountTitle + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListFragment.kt new file mode 100644 index 0000000000..7331467104 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListFragment.kt @@ -0,0 +1,88 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list + +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.BaseFragment +import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.utils.applyStatusBarInsets +import io.novafoundation.nova.feature_account_api.presenatation.actions.CustomizableExternalActionsSheet +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActionModel +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_account_api.presenatation.actions.copyAddressClicked +import io.novafoundation.nova.feature_account_api.presenatation.actions.setupExternalActions +import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi +import io.novafoundation.nova.feature_staking_impl.R +import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureComponent +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.model.StakingProxyRvItem +import javax.inject.Inject +import kotlinx.android.synthetic.main.fragment_staking_proxy_list.stakingProxyList +import kotlinx.android.synthetic.main.fragment_staking_proxy_list.stakingProxyListAddProxyButton +import kotlinx.android.synthetic.main.fragment_staking_proxy_list.stakingProxyListToolbar + +class StakingProxyListFragment : BaseFragment(), StakingProxyListAdapter.Handler { + + @Inject + protected lateinit var imageLoader: ImageLoader + + private val adapter by lazy(LazyThreadSafetyMode.NONE) { StakingProxyListAdapter(this, imageLoader) } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ): View? { + return inflater.inflate(R.layout.fragment_staking_proxy_list, container, false) + } + + override fun initViews() { + stakingProxyListToolbar.applyStatusBarInsets() + stakingProxyListToolbar.setHomeButtonListener { viewModel.backClicked() } + + stakingProxyListAddProxyButton.setOnClickListener { viewModel.addProxyClicked() } + + stakingProxyList.adapter = adapter + } + + override fun inject() { + FeatureUtils.getFeature( + requireContext(), + StakingFeatureApi::class.java + ) + .stakingProxyListFactory() + .create(this) + .inject(this) + } + + override fun subscribe(viewModel: StakingProxyListViewModel) { + setupExternalActions(viewModel) { context, payload -> + CustomizableExternalActionsSheet( + context, + payload, + onCopy = viewModel::copyAddressClicked, + onViewExternal = viewModel::viewExternalClicked, + additionalOptions = rewokeAccessExternalAction(payload) + ) + } + + viewModel.proxyModels.observe { + adapter.submitList(it) + } + } + + override fun onProxyClick(item: StakingProxyRvItem) { + viewModel.proxyClicked(item) + } + + private fun rewokeAccessExternalAction(payload: ExternalActions.Payload): List { + return listOf( + ExternalActionModel( + R.drawable.ic_delete, + getString(R.string.common_proxy_rewoke_access), + onClick = { viewModel.rewokeAccess(payload) } + ) + ) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListViewModel.kt new file mode 100644 index 0000000000..819ab9023c --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/StakingProxyListViewModel.kt @@ -0,0 +1,93 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list + +import io.novafoundation.nova.common.address.AddressIconGenerator +import io.novafoundation.nova.common.address.AddressIconGenerator.Companion.SIZE_BIG +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.utils.castOrNull +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.domain.model.requireAccountIdIn +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_account_api.presenatation.actions.showAddressActions +import io.novafoundation.nova.feature_staking_impl.R +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.list.StakingProxyListInteractor +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.list.model.StakingProxyAccount +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.model.StakingProxyGroupRvItem +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.model.StakingProxyRvItem +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyPayload +import io.novafoundation.nova.runtime.ext.addressOf +import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState +import io.novafoundation.nova.runtime.state.chain +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class StakingProxyListViewModel( + private val selectedAssetState: AnySelectedAssetOptionSharedState, + private val externalActions: ExternalActions.Presentation, + private val accountRepository: AccountRepository, + private val stakingProxyListInteractor: StakingProxyListInteractor, + private val resourceManager: ResourceManager, + private val stakingRouter: StakingRouter, + private val addressIconGenerator: AddressIconGenerator +) : BaseViewModel(), ExternalActions by externalActions { + + val selectedMetaAccount = accountRepository.selectedMetaAccountFlow() + .shareInBackground() + + val proxies = selectedMetaAccount.flatMapLatest { + val chain = selectedAssetState.chain() + val accountId = it.requireAccountIdIn(chain) + stakingProxyListInteractor.stakingProxyListFlow(chain, accountId) + } + .shareInBackground() + + val proxyModels: Flow> = proxies.map { + mapToProxyList(it) + } + .shareInBackground() + + fun backClicked() { + stakingRouter.back() + } + + fun addProxyClicked() { + stakingRouter.openAddStakingProxy() + } + + fun proxyClicked(item: StakingProxyRvItem) { + launch { + val chain = selectedAssetState.chain() + externalActions.showAddressActions(item.accountAddress, chain) + } + } + + fun rewokeAccess(externalActionPayload: ExternalActions.Payload) { + val payload = ConfirmRemoveStakingProxyPayload(externalActionPayload.requireAddress()) + stakingRouter.openConfirmRemoveStakingProxy(payload) + } + + private suspend fun mapToProxyList(proxies: List): List { + val chain = selectedAssetState.chain() + return buildList { + val groupTitle = resourceManager.getString(R.string.staking_proxies_group_title) + add(StakingProxyGroupRvItem(groupTitle)) + + val proxyRvItems = proxies.map { stakingProxyAccount -> + val accountAddress = chain.addressOf(stakingProxyAccount.proxyAccountId) + StakingProxyRvItem( + addressIconGenerator.createAddressIcon(stakingProxyAccount.proxyAccountId, SIZE_BIG), + chain.icon, + stakingProxyAccount.accountName, + accountAddress + ) + } + + addAll(proxyRvItems) + } + } + + private fun ExternalActions.Payload.requireAddress() = type.castOrNull()!!.address!! +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/di/StakingProxyListComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/di/StakingProxyListComponent.kt new file mode 100644 index 0000000000..c47fa60b30 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/di/StakingProxyListComponent.kt @@ -0,0 +1,23 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.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_staking_impl.presentation.staking.delegation.proxy.list.StakingProxyListFragment + +@Subcomponent( + modules = [ + StakingProxyListModule::class + ] +) +@ScreenScope +interface StakingProxyListComponent { + + @Subcomponent.Factory + interface Factory { + fun create(@BindsInstance fragment: Fragment): StakingProxyListComponent + } + + fun inject(fragment: StakingProxyListFragment) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/di/StakingProxyListModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/di/StakingProxyListModule.kt new file mode 100644 index 0000000000..9817146824 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/di/StakingProxyListModule.kt @@ -0,0 +1,53 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.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.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.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.list.StakingProxyListInteractor +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.StakingProxyListViewModel +import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState + +@Module(includes = [ViewModelModule::class]) +class StakingProxyListModule { + + @Provides + @IntoMap + @ViewModelKey(StakingProxyListViewModel::class) + fun provideViewModel( + selectedAssetState: AnySelectedAssetOptionSharedState, + externalActions: ExternalActions.Presentation, + accountRepository: AccountRepository, + resourceManager: ResourceManager, + stakingRouter: StakingRouter, + addressIconGenerator: AddressIconGenerator, + stakingProxyListInteractor: StakingProxyListInteractor + ): ViewModel { + return StakingProxyListViewModel( + selectedAssetState = selectedAssetState, + externalActions = externalActions, + accountRepository = accountRepository, + resourceManager = resourceManager, + stakingRouter = stakingRouter, + addressIconGenerator = addressIconGenerator, + stakingProxyListInteractor = stakingProxyListInteractor + ) + } + + @Provides + fun provideViewModelCreator( + fragment: Fragment, + viewModelFactory: ViewModelProvider.Factory + ): StakingProxyListViewModel { + return ViewModelProvider(fragment, viewModelFactory).get(StakingProxyListViewModel::class.java) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/model/StakingProxyListModels.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/model/StakingProxyListModels.kt new file mode 100644 index 0000000000..9f88f3d135 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/list/model/StakingProxyListModels.kt @@ -0,0 +1,12 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.list.model + +import android.graphics.drawable.Drawable + +class StakingProxyGroupRvItem(val text: String) + +class StakingProxyRvItem( + val accountIcon: Drawable, + val chainIconUrl: String, + val accountTitle: String, + val accountAddress: String +) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyFragment.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyFragment.kt new file mode 100644 index 0000000000..130a87cd8e --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyFragment.kt @@ -0,0 +1,82 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import io.novafoundation.nova.common.base.BaseFragment +import io.novafoundation.nova.common.di.FeatureUtils +import io.novafoundation.nova.common.mixin.impl.observeValidations +import io.novafoundation.nova.common.utils.applyStatusBarInsets +import io.novafoundation.nova.common.view.setProgress +import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.showWallet +import io.novafoundation.nova.feature_account_api.presenatation.actions.setupExternalActions +import io.novafoundation.nova.feature_account_api.view.showAddress +import io.novafoundation.nova.feature_account_api.view.showChain +import io.novafoundation.nova.feature_staking_api.di.StakingFeatureApi +import io.novafoundation.nova.feature_staking_impl.R +import io.novafoundation.nova.feature_staking_impl.di.StakingFeatureComponent +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.setupFeeLoading +import kotlinx.android.synthetic.main.fragment_confirm_revoke_staking_proxy.confirmRemoveStakingProxyButton +import kotlinx.android.synthetic.main.fragment_confirm_revoke_staking_proxy.confirmRemoveStakingProxyDelegationAccount +import kotlinx.android.synthetic.main.fragment_confirm_revoke_staking_proxy.confirmRemoveStakingProxyNetwork +import kotlinx.android.synthetic.main.fragment_confirm_revoke_staking_proxy.confirmRemoveStakingProxyNetworkFee +import kotlinx.android.synthetic.main.fragment_confirm_revoke_staking_proxy.confirmRemoveStakingProxyProxiedAccount +import kotlinx.android.synthetic.main.fragment_confirm_revoke_staking_proxy.confirmRemoveStakingProxyToolbar +import kotlinx.android.synthetic.main.fragment_confirm_revoke_staking_proxy.confirmRemoveStakingProxyWallet + +class ConfirmRemoveStakingProxyFragment : BaseFragment() { + companion object { + + private const val PAYLOAD_KEY = "PAYLOAD_KEY" + + fun getBundle(payload: ConfirmRemoveStakingProxyPayload) = Bundle().apply { + putParcelable(PAYLOAD_KEY, payload) + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_confirm_revoke_staking_proxy, container, false) + } + + override fun initViews() { + confirmRemoveStakingProxyToolbar.applyStatusBarInsets() + + confirmRemoveStakingProxyToolbar.setHomeButtonListener { viewModel.backClicked() } + + confirmRemoveStakingProxyButton.setOnClickListener { viewModel.confirmClicked() } + confirmRemoveStakingProxyButton.prepareForProgress(viewLifecycleOwner) + + confirmRemoveStakingProxyProxiedAccount.setOnClickListener { viewModel.proxiedAccountClicked() } + confirmRemoveStakingProxyDelegationAccount.setOnClickListener { viewModel.proxyAccountClicked() } + } + + override fun inject() { + val payload = argument(PAYLOAD_KEY) + + FeatureUtils.getFeature( + requireContext(), + StakingFeatureApi::class.java + ) + .confirmRevokeStakingProxyFactory() + .create(this, payload) + .inject(this) + } + + override fun subscribe(viewModel: ConfirmRemoveStakingProxyViewModel) { + observeValidations(viewModel) + setupExternalActions(viewModel) + setupFeeLoading(viewModel.feeMixin, confirmRemoveStakingProxyNetworkFee) + + viewModel.chainModel.observe { confirmRemoveStakingProxyNetwork.showChain(it) } + viewModel.walletUiFlow.observe { confirmRemoveStakingProxyWallet.showWallet(it) } + viewModel.proxiedAccountModel.observe { confirmRemoveStakingProxyProxiedAccount.showAddress(it) } + viewModel.proxyAccountModel.observe { confirmRemoveStakingProxyDelegationAccount.showAddress(it) } + + viewModel.validationProgressFlow.observe(confirmRemoveStakingProxyButton::setProgress) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyPayload.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyPayload.kt new file mode 100644 index 0000000000..3fd2f84143 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyPayload.kt @@ -0,0 +1,9 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +class ConfirmRemoveStakingProxyPayload( + val proxyAddress: String +) : Parcelable diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyViewModel.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyViewModel.kt new file mode 100644 index 0000000000..f781098f50 --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/ConfirmRemoveStakingProxyViewModel.kt @@ -0,0 +1,160 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke + +import io.novafoundation.nova.common.address.AddressIconGenerator +import io.novafoundation.nova.common.base.BaseViewModel +import io.novafoundation.nova.common.mixin.api.Validatable +import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.common.validation.ValidationExecutor +import io.novafoundation.nova.common.validation.progressConsumer +import io.novafoundation.nova.feature_account_api.data.mappers.mapChainToUi +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +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.icon.createAccountAddressModel +import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.remove.RemoveStakingProxyInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.RemoveStakingProxyValidationPayload +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.RemoveStakingProxyValidationSystem +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.common.mapRemoveStakingProxyValidationFailureToUi +import io.novafoundation.nova.feature_wallet_api.domain.ArbitraryAssetUseCase +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.create +import io.novafoundation.nova.runtime.ext.accountIdOf +import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain +import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState +import io.novafoundation.nova.runtime.state.chain +import io.novafoundation.nova.runtime.state.selectedAssetFlow +import io.novafoundation.nova.runtime.state.selectedChainFlow +import jp.co.soramitsu.fearless_utils.runtime.AccountId +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class ConfirmRemoveStakingProxyViewModel( + private val router: StakingRouter, + private val addressIconGenerator: AddressIconGenerator, + private val payload: ConfirmRemoveStakingProxyPayload, + private val accountRepository: AccountRepository, + private val resourceManager: ResourceManager, + private val externalActions: ExternalActions.Presentation, + private val validationExecutor: ValidationExecutor, + private val selectedAssetState: AnySelectedAssetOptionSharedState, + private val assetUseCase: ArbitraryAssetUseCase, + private val removeStakingProxyValidationSystem: RemoveStakingProxyValidationSystem, + private val walletUiUseCase: WalletUiUseCase, + private val removeStakingProxyInteractor: RemoveStakingProxyInteractor, + private val feeLoaderMixinFactory: FeeLoaderMixin.Factory +) : BaseViewModel(), + Validatable by validationExecutor, + ExternalActions by externalActions { + + private val selectedMetaAccountFlow = accountRepository.selectedMetaAccountFlow() + .shareInBackground() + + private val chainFlow = selectedAssetState.selectedChainFlow() + .shareInBackground() + + @OptIn(ExperimentalCoroutinesApi::class) + private val assetFlow = selectedAssetState.selectedAssetFlow() + .flatMapLatest { assetUseCase.assetFlow(it) } + .shareInBackground() + + val feeMixin: FeeLoaderMixin.Presentation = feeLoaderMixinFactory.create(assetFlow) + + val chainModel = chainFlow.map { chain -> + mapChainToUi(chain) + } + + val walletUiFlow = selectedMetaAccountFlow.map { walletUiUseCase.walletUiFor(it) } + + val proxiedAccountModel = combine(selectedMetaAccountFlow, chainFlow) { metaAccount, chain -> + val address = metaAccount.requireAddressIn(chain) + + generateAccountAddressModel(chain, address) + } + + val proxyAccountModel = chainFlow.map { chain -> + generateAccountAddressModel(chain, payload.proxyAddress) + } + + val validationProgressFlow = MutableStateFlow(false) + + init { + loadFee() + } + + fun backClicked() { + router.back() + } + + fun proxiedAccountClicked() { + launch { + showExternalActions(proxiedAccountModel.first().address) + } + } + + fun proxyAccountClicked() { + showExternalActions(payload.proxyAddress) + } + + fun confirmClicked() = launch { + val metaAccount = accountRepository.getSelectedMetaAccount() + val chain = selectedAssetState.chain() + val validationPayload = RemoveStakingProxyValidationPayload( + chain = chain, + asset = assetFlow.first(), + proxyAddress = payload.proxyAddress, + proxiedAccountId = metaAccount.requireAccountIdIn(chain), + fee = feeMixin.awaitDecimalFee() + ) + + validationExecutor.requireValid( + validationSystem = removeStakingProxyValidationSystem, + payload = validationPayload, + validationFailureTransformer = { mapRemoveStakingProxyValidationFailureToUi(resourceManager, it) }, + progressConsumer = validationProgressFlow.progressConsumer() + ) { + sendTransaction(it.chain, it.proxiedAccountId, it.chain.accountIdOf(it.proxyAddress)) + } + } + + private fun loadFee() { + launch { + val metaAccount = selectedMetaAccountFlow.first() + val chain = selectedAssetState.chain() + val proxiedAccountId = metaAccount.requireAccountIdIn(chain) + + feeMixin.loadFee( + this, + chain.id, + feeConstructor = { removeStakingProxyInteractor.estimateFee(chain, proxiedAccountId) }, + onRetryCancelled = ::backClicked + ) + } + } + + private fun sendTransaction(chain: Chain, proxiedAccount: AccountId, proxyAccount: AccountId) = launch { + val result = removeStakingProxyInteractor.removeProxy(chain, proxiedAccount, proxyAccount) + + validationProgressFlow.value = false + + result.onSuccess { router.returnToStakingMain() } + .onFailure(::showError) + } + + private suspend fun generateAccountAddressModel(chain: Chain, address: String) = addressIconGenerator.createAccountAddressModel( + chain = chain, + address = address, + ) + + private fun showExternalActions(address: String) = launch { + externalActions.showExternalActions(ExternalActions.Type.Address(address), selectedAssetState.chain()) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/di/ConfirmRemoveStakingProxyComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/di/ConfirmRemoveStakingProxyComponent.kt new file mode 100644 index 0000000000..d4a5aab73a --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/di/ConfirmRemoveStakingProxyComponent.kt @@ -0,0 +1,27 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.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_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyFragment +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyPayload + +@Subcomponent( + modules = [ + ConfirmRemoveStakingProxyModule::class + ] +) +@ScreenScope +interface ConfirmRemoveStakingProxyComponent { + + @Subcomponent.Factory + interface Factory { + fun create( + @BindsInstance fragment: Fragment, + @BindsInstance payload: ConfirmRemoveStakingProxyPayload, + ): ConfirmRemoveStakingProxyComponent + } + + fun inject(fragment: ConfirmRemoveStakingProxyFragment) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/di/ConfirmRemoveStakingProxyModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/di/ConfirmRemoveStakingProxyModule.kt new file mode 100644 index 0000000000..a5fc0ce73b --- /dev/null +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/delegation/proxy/revoke/di/ConfirmRemoveStakingProxyModule.kt @@ -0,0 +1,70 @@ +package io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.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.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.validation.ValidationExecutor +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_account_api.presenatation.account.wallet.WalletUiUseCase +import io.novafoundation.nova.feature_account_api.presenatation.actions.ExternalActions +import io.novafoundation.nova.feature_staking_impl.domain.staking.delegation.proxy.remove.RemoveStakingProxyInteractor +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.proxy.remove.RemoveStakingProxyValidationSystem +import io.novafoundation.nova.feature_staking_impl.presentation.StakingRouter +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyPayload +import io.novafoundation.nova.feature_staking_impl.presentation.staking.delegation.proxy.revoke.ConfirmRemoveStakingProxyViewModel +import io.novafoundation.nova.feature_wallet_api.domain.ArbitraryAssetUseCase +import io.novafoundation.nova.feature_wallet_api.presentation.mixin.fee.FeeLoaderMixin +import io.novafoundation.nova.runtime.state.AnySelectedAssetOptionSharedState + +@Module(includes = [ViewModelModule::class]) +class ConfirmRemoveStakingProxyModule { + @Provides + @IntoMap + @ViewModelKey(ConfirmRemoveStakingProxyViewModel::class) + fun provideViewModule( + router: StakingRouter, + addressIconGenerator: AddressIconGenerator, + payload: ConfirmRemoveStakingProxyPayload, + accountRepository: AccountRepository, + resourceManager: ResourceManager, + externalActions: ExternalActions.Presentation, + validationExecutor: ValidationExecutor, + selectedAssetState: AnySelectedAssetOptionSharedState, + assetUseCase: ArbitraryAssetUseCase, + walletUiUseCase: WalletUiUseCase, + removeStakingProxyInteractor: RemoveStakingProxyInteractor, + removeStakingProxyValidationSystem: RemoveStakingProxyValidationSystem, + feeLoaderMixinFactory: FeeLoaderMixin.Factory + ): ViewModel { + return ConfirmRemoveStakingProxyViewModel( + router = router, + addressIconGenerator = addressIconGenerator, + payload = payload, + accountRepository = accountRepository, + resourceManager = resourceManager, + externalActions = externalActions, + validationExecutor = validationExecutor, + selectedAssetState = selectedAssetState, + assetUseCase = assetUseCase, + walletUiUseCase = walletUiUseCase, + removeStakingProxyInteractor = removeStakingProxyInteractor, + removeStakingProxyValidationSystem = removeStakingProxyValidationSystem, + feeLoaderMixinFactory = feeLoaderMixinFactory + ) + } + + @Provides + fun provideViewModelCreator( + fragment: Fragment, + viewModelFactory: ViewModelProvider.Factory + ): ConfirmRemoveStakingProxyViewModel { + return ViewModelProvider(fragment, viewModelFactory).get(ConfirmRemoveStakingProxyViewModel::class.java) + } +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/ValidationFailure.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/ValidationFailure.kt index 4e31295ec8..0a87047bfe 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/ValidationFailure.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/ValidationFailure.kt @@ -19,9 +19,15 @@ fun mainStakingValidationFailure( getString(R.string.staking_unbonding_limit_reached_title) to getString(R.string.staking_unbonding_limit_reached_message, reason.limit) } + is StakeActionsValidationFailure.StashRequired -> { getString(R.string.common_error_general_title) to getString(R.string.staking_stash_missing_message, reason.stashAddress) } + + is StakeActionsValidationFailure.StashRequiredToManageProxies -> { + getString(R.string.staking_manage_proxy_requires_stash_title) to + getString(R.string.staking_manage_proxy_requires_stash_message, reason.stashMetaAccount?.name ?: reason.stashAddress) + } } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/ManageStakeAction.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/ManageStakeAction.kt index 8219ed8037..4d05f40eb0 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/ManageStakeAction.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/ManageStakeAction.kt @@ -3,8 +3,10 @@ package io.novafoundation.nova.feature_staking_impl.presentation.staking.main.co import androidx.annotation.DrawableRes import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_staking_impl.R +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_ADD_PROXY import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_CONTROLLER import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_PAYOUTS +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_PROXIES import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_REWARD_DESTINATION import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_STAKING_BOND_MORE import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_STAKING_UNBOND @@ -66,3 +68,20 @@ fun ManageStakeAction.Companion.controller(resourceManager: ResourceManager): Ma iconRes = R.drawable.ic_people_outline ) } + +fun ManageStakeAction.Companion.addStakingProxy(resourceManager: ResourceManager): ManageStakeAction { + return ManageStakeAction( + id = SYSTEM_ADD_PROXY, + label = resourceManager.getString(R.string.staking_action_add_proxy), + iconRes = R.drawable.ic_delegate_outline + ) +} + +fun ManageStakeAction.Companion.stakingProxies(resourceManager: ResourceManager, delegations: String): ManageStakeAction { + return ManageStakeAction( + id = SYSTEM_MANAGE_PROXIES, + label = resourceManager.getString(R.string.staking_action_your_proxies), + iconRes = R.drawable.ic_delegate_outline, + badge = delegations + ) +} diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/nominationPools/NominationPoolsStakeActionsComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/nominationPools/NominationPoolsStakeActionsComponent.kt index 81e14b8e61..13747b8e1d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/nominationPools/NominationPoolsStakeActionsComponent.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/nominationPools/NominationPoolsStakeActionsComponent.kt @@ -7,6 +7,7 @@ import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.WithCoroutineScopeExtensions import io.novafoundation.nova.common.utils.flowOf import io.novafoundation.nova.feature_staking_impl.data.StakingOption +import io.novafoundation.nova.feature_staking_impl.data.chain import io.novafoundation.nova.feature_staking_impl.data.nominationPools.network.blockhain.models.PoolMember import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.common.NominationPoolSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_STAKING_BOND_MORE diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/relaychain/RelaychainStakeActionsComponent.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/relaychain/RelaychainStakeActionsComponent.kt index 145d5a7e59..719af99b56 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/relaychain/RelaychainStakeActionsComponent.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/components/stakeActions/relaychain/RelaychainStakeActionsComponent.kt @@ -4,11 +4,19 @@ import androidx.lifecycle.MutableLiveData import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.utils.Event import io.novafoundation.nova.common.utils.WithCoroutineScopeExtensions +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.requireAccountIdIn +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository +import io.novafoundation.nova.feature_proxy_api.domain.model.ProxyType import io.novafoundation.nova.feature_staking_api.domain.model.relaychain.StakingState import io.novafoundation.nova.feature_staking_impl.data.StakingOption +import io.novafoundation.nova.feature_staking_impl.data.chain import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_ADD_PROXY import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_CONTROLLER import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_PAYOUTS +import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_PROXIES import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_REWARD_DESTINATION import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_STAKING_BOND_MORE import io.novafoundation.nova.feature_staking_impl.domain.validations.main.SYSTEM_MANAGE_STAKING_UNBOND @@ -22,17 +30,23 @@ import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.com import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.StakeActionsComponent import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.StakeActionsEvent import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.StakeActionsState +import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.addStakingProxy import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.bondMore import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.controller import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.payouts import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.rewardDestination +import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.stakingProxies import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.unbond import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.components.stakeActions.validators import io.novafoundation.nova.feature_staking_impl.presentation.staking.main.mainStakingValidationFailure import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.transformLatest +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch class RelaychainStakeActionsComponentFactory( @@ -40,6 +54,8 @@ class RelaychainStakeActionsComponentFactory( private val resourceManager: ResourceManager, private val stakeActionsValidations: Map, private val router: StakingRouter, + private val accountRepository: AccountRepository, + private val getProxyRepository: GetProxyRepository ) { fun create( @@ -51,7 +67,9 @@ class RelaychainStakeActionsComponentFactory( router = router, stakeActionsValidations = stakeActionsValidations, stakingOption = stakingOption, - hostContext = hostContext + hostContext = hostContext, + accountRepository = accountRepository, + getProxyRepository = getProxyRepository ) } @@ -60,23 +78,33 @@ private class RelaychainStakeActionsComponent( private val resourceManager: ResourceManager, private val router: StakingRouter, private val stakeActionsValidations: Map, - private val hostContext: ComponentHostContext, private val stakingOption: StakingOption, + private val accountRepository: AccountRepository, + private val getProxyRepository: GetProxyRepository ) : StakeActionsComponent, CoroutineScope by hostContext.scope, WithCoroutineScopeExtensions by WithCoroutineScopeExtensions(hostContext.scope) { override val events = MutableLiveData>() + @OptIn(ExperimentalCoroutinesApi::class) + private val stakingProxiesQuantity = accountRepository.selectedMetaAccountFlow() + .flatMapLatest { metaAccount -> + getProxiesQuantity(metaAccount) + } + private val selectedAccountStakingStateFlow = stakingSharedComputation.selectedAccountStakingStateFlow( assetWithChain = stakingOption.assetWithChain, scope = hostContext.scope ) - override val state = selectedAccountStakingStateFlow.transformLatest { stakingState -> + override val state = combineTransform( + selectedAccountStakingStateFlow, + stakingProxiesQuantity + ) { stakingState, proxiesQuantity -> if (stakingState is StakingState.Stash) { - emit(StakeActionsState(availableActionsFor(stakingState))) + emit(StakeActionsState(availableActionsFor(stakingState, proxiesQuantity))) } else { emit(null) } @@ -89,6 +117,14 @@ private class RelaychainStakeActionsComponent( } } + private suspend fun getProxiesQuantity(metaAccount: MetaAccount): Flow { + val chain = stakingOption.assetWithChain.chain + if (chain.supportProxy.not()) return flowOf(0) + + val accountId = metaAccount.requireAccountIdIn(chain) + return getProxyRepository.proxiesQuantityByTypeFlow(chain, accountId, ProxyType.Staking) + } + private fun manageStakeActionChosen(manageStakeAction: ManageStakeAction) { val validationSystem = stakeActionsValidations[manageStakeAction.id] @@ -120,21 +156,36 @@ private class RelaychainStakeActionsComponent( SYSTEM_MANAGE_CONTROLLER -> router.openControllerAccount() SYSTEM_MANAGE_VALIDATORS -> router.openCurrentValidators() SYSTEM_MANAGE_REWARD_DESTINATION -> router.openChangeRewardDestination() + SYSTEM_ADD_PROXY -> router.openAddStakingProxy() + SYSTEM_MANAGE_PROXIES -> router.openStakingProxyList() } } - private fun availableActionsFor(stakingState: StakingState.Stash): List = buildList { - add(ManageStakeAction.Companion::bondMore) - add(ManageStakeAction.Companion::unbond) - add(ManageStakeAction.Companion::rewardDestination) - add(ManageStakeAction.Companion::controller) + private fun availableActionsFor(stakingState: StakingState.Stash, proxiesQuantity: Int): List = buildList { + add(ManageStakeAction.bondMore(resourceManager)) + add(ManageStakeAction.unbond(resourceManager)) + add(ManageStakeAction.rewardDestination(resourceManager)) if (stakingState !is StakingState.Stash.None) { - add(ManageStakeAction.Companion::payouts) + add(ManageStakeAction.payouts(resourceManager)) } if (stakingState !is StakingState.Stash.Validator) { - add(ManageStakeAction.Companion::validators) + add(ManageStakeAction.validators(resourceManager)) + } + + if (stakingOption.chain.supportProxy) { + add(proxiesAction(proxiesQuantity)) } - }.map { it.invoke(resourceManager) } + + add(ManageStakeAction.controller(resourceManager)) + } + + private fun proxiesAction(proxiesQuantity: Int): ManageStakeAction { + return if (proxiesQuantity == 0) { + ManageStakeAction.addStakingProxy(resourceManager) + } else { + ManageStakeAction.stakingProxies(resourceManager, proxiesQuantity.toString()) + } + } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/di/components/RelaychainModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/di/components/RelaychainModule.kt index 6440ad1328..b9f7ccf18e 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/di/components/RelaychainModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/presentation/staking/main/di/components/RelaychainModule.kt @@ -5,6 +5,8 @@ import dagger.Provides import io.novafoundation.nova.common.di.scope.ScreenScope import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.common.validation.ValidationExecutor +import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository +import io.novafoundation.nova.feature_proxy_api.data.repository.GetProxyRepository import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor import io.novafoundation.nova.feature_staking_impl.domain.alerts.AlertsInteractor import io.novafoundation.nova.feature_staking_impl.domain.common.StakingSharedComputation @@ -66,11 +68,15 @@ class RelaychainModule { resourceManager: ResourceManager, stakeActionsValidations: Map<@JvmSuppressWildcards String, StakeActionsValidationSystem>, router: StakingRouter, + accountRepository: AccountRepository, + getProxyRepository: GetProxyRepository ) = RelaychainStakeActionsComponentFactory( stakingSharedComputation = stakingSharedComputation, resourceManager = resourceManager, stakeActionsValidations = stakeActionsValidations, - router = router + router = router, + accountRepository = accountRepository, + getProxyRepository = getProxyRepository ) @Provides 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 55afbd8826..3b9667a04c 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 @@ -3,7 +3,7 @@ package io.novafoundation.nova.feature_staking_impl.presentation.validators.chan import io.novafoundation.nova.common.base.TitleAndMessage import io.novafoundation.nova.common.resources.ResourceManager import io.novafoundation.nova.feature_staking_impl.R -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.ChangeStackingValidationFailure +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.ChangeStackingValidationFailure fun mapAddEvmTokensValidationFailureToUI( resourceManager: ResourceManager, 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 8ec5486d08..fdb3e2b9b4 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 @@ -16,7 +16,7 @@ import io.novafoundation.nova.feature_staking_api.domain.model.NominatedValidato 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.domain.StakingInteractor -import io.novafoundation.nova.feature_staking_impl.domain.validations.controller.ChangeStackingValidationPayload +import io.novafoundation.nova.feature_staking_impl.domain.validations.delegation.controller.ChangeStackingValidationPayload 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 diff --git a/feature-staking-impl/src/main/res/layout/fragment_add_staking_proxy.xml b/feature-staking-impl/src/main/res/layout/fragment_add_staking_proxy.xml new file mode 100644 index 0000000000..751c2fc41b --- /dev/null +++ b/feature-staking-impl/src/main/res/layout/fragment_add_staking_proxy.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/fragment_confirm_add_staking_proxy.xml b/feature-staking-impl/src/main/res/layout/fragment_confirm_add_staking_proxy.xml new file mode 100644 index 0000000000..8b07847b0b --- /dev/null +++ b/feature-staking-impl/src/main/res/layout/fragment_confirm_add_staking_proxy.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/fragment_confirm_revoke_staking_proxy.xml b/feature-staking-impl/src/main/res/layout/fragment_confirm_revoke_staking_proxy.xml new file mode 100644 index 0000000000..6510d6ee33 --- /dev/null +++ b/feature-staking-impl/src/main/res/layout/fragment_confirm_revoke_staking_proxy.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/fragment_staking_proxy_list.xml b/feature-staking-impl/src/main/res/layout/fragment_staking_proxy_list.xml new file mode 100644 index 0000000000..b0f58d2fbf --- /dev/null +++ b/feature-staking-impl/src/main/res/layout/fragment_staking_proxy_list.xml @@ -0,0 +1,37 @@ + + + + + + + + + + \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/item_proxy.xml b/feature-staking-impl/src/main/res/layout/item_proxy.xml new file mode 100644 index 0000000000..363ad6c761 --- /dev/null +++ b/feature-staking-impl/src/main/res/layout/item_proxy.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/feature-staking-impl/src/main/res/layout/item_proxy_group.xml b/feature-staking-impl/src/main/res/layout/item_proxy_group.xml new file mode 100644 index 0000000000..e023874e0a --- /dev/null +++ b/feature-staking-impl/src/main/res/layout/item_proxy_group.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/Asset.kt b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/Asset.kt index 173959ed0c..914a90f75f 100644 --- a/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/Asset.kt +++ b/feature-wallet-api/src/main/java/io/novafoundation/nova/feature_wallet_api/domain/model/Asset.kt @@ -98,6 +98,7 @@ data class Asset( val transferable = token.amountFromPlanks(transferableInPlanks) val free = token.amountFromPlanks(freeInPlanks) + val frozen = token.amountFromPlanks(frozenInPlanks) // TODO move to runtime storage val bonded = token.amountFromPlanks(bondedInPlanks) @@ -112,3 +113,7 @@ fun Asset.balanceCountedTowardsED(): BigDecimal { fun Asset.transferableReplacingFrozen(newFrozen: Balance): Balance { return transferableMode.calculateTransferable(freeInPlanks, newFrozen, reservedInPlanks) } + +fun Asset.regularTransferableBalance(): Balance { + return Asset.TransferableMode.REGULAR.calculateTransferable(freeInPlanks, frozenInPlanks, reservedInPlanks) +} 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 2d0b5b66ec..c175306857 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 @@ -48,7 +48,7 @@ class EnoughAmountToTransferValidationGeneric( val payload: P, - val availableToPayFees: BigDecimal, + val maxUsable: BigDecimal, val fee: BigDecimal, ) 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 69f00884f4..a36a1c0b83 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 @@ -95,7 +95,7 @@ fun AssetTransfersValidationSystemBuilder.sufficientTransferableBalanceToPayOrig AssetTransferValidationFailure.NotEnoughFunds.InCommissionAsset( chainAsset = context.payload.transfer.originChain.commissionAsset, fee = context.fee, - maxUsable = context.availableToPayFees + maxUsable = context.maxUsable ) } ) 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 23d04326ca..9068d274d6 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 @@ -3,10 +3,13 @@ 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 +import jp.co.soramitsu.fearless_utils.runtime.extrinsic.signer.SignerPayloadExtrinsic interface NovaSigner : Signer { suspend fun signerAccountId(chain: Chain): AccountId + + suspend fun modifyPayload(payloadExtrinsic: SignerPayloadExtrinsic): SignerPayloadExtrinsic } interface FeeSigner : NovaSigner { @@ -23,4 +26,8 @@ interface FeeSigner : NovaSigner { * It might not be equal to [actualFeeSignerId] if [Signer] modifies the payload */ suspend fun requestedFeeSignerId(chain: Chain): AccountId + + override suspend fun modifyPayload(payloadExtrinsic: SignerPayloadExtrinsic): SignerPayloadExtrinsic { + throw NotImplementedError("This method should not be called") + } } diff --git a/settings.gradle b/settings.gradle index 60b8b164f3..dbe2e34e9b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,3 +36,5 @@ include ':feature-swap-api' include ':feature-swap-impl' include ':feature-buy-api' include ':feature-buy-impl' +include ':feature-proxy-impl' +include ':feature-proxy-api'