diff --git a/common/src/main/java/io/novafoundation/nova/common/data/memory/RealComputationalCache.kt b/common/src/main/java/io/novafoundation/nova/common/data/memory/RealComputationalCache.kt index feba1dd..12f4b11 100644 --- a/common/src/main/java/io/novafoundation/nova/common/data/memory/RealComputationalCache.kt +++ b/common/src/main/java/io/novafoundation/nova/common/data/memory/RealComputationalCache.kt @@ -6,7 +6,6 @@ import io.novafoundation.nova.common.utils.flowOfAll import io.novafoundation.nova.common.utils.inBackground import io.novafoundation.nova.common.utils.invokeOnCompletion import io.novafoundation.nova.common.utils.singleReplaySharedFlow -import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -14,7 +13,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -74,25 +72,18 @@ internal class RealComputationalCache : ComputationalCache, CoroutineScope by Co cachedAction: AwaitableConstructor ): T { val awaitable = mutex.withLock { - val existing = memory[key] - if (existing != null && existing.aggregateScope.isActive) { + if (key in memory) { Log.d(LOG_TAG, "Key $key requested - already present") - existing.dependents += scope + val entry = memory.getValue(key) - existing.awaitable + entry.dependents += scope + + entry.awaitable } else { - if (existing != null) { - Log.d(LOG_TAG, "Key $key requested - stale (aggregateScope cancelled), recreating") - memory.remove(key) - } else { - Log.d(LOG_TAG, "Key $key requested - creating new operation") - } + Log.d(LOG_TAG, "Key $key requested - creating new operation") - val exceptionHandler = CoroutineExceptionHandler { _, throwable -> - Log.e(LOG_TAG, "Key $key - upstream error in aggregateScope", throwable) - } - val aggregateScope = CoroutineScope(Dispatchers.Default + exceptionHandler) + val aggregateScope = CoroutineScope(Dispatchers.Default) val awaitable = cachedAction(aggregateScope) memory[key] = Entry(dependents = mutableSetOf(scope), aggregateScope, awaitable) diff --git a/common/src/main/res/drawable-xxhdpi/telegram_welcome.png b/common/src/main/res/drawable-xxhdpi/telegram_welcome.png new file mode 100644 index 0000000..f0435f4 Binary files /dev/null and b/common/src/main/res/drawable-xxhdpi/telegram_welcome.png differ diff --git a/common/src/main/res/values-es/strings.xml b/common/src/main/res/values-es/strings.xml index 0ea856d..b6a19db 100644 --- a/common/src/main/res/values-es/strings.xml +++ b/common/src/main/res/values-es/strings.xml @@ -2029,6 +2029,7 @@ Puente DOT ↔ HEZ + Puente USDT(DOT) ↔ USDT(HEZ) Puente DOT ↔ HEZ Envías Recibes (estimado) @@ -2048,4 +2049,5 @@ Pezkuwi Puntuación de confianza Solicitar y Acciones + Usa nuestro Telegram MiniApp para servicios de ciudadanía del Kurdistán Digital.\n\nPara ganar recompensas PEZ, debes tener un ticket Welatî y haber apostado al menos 10 HEZ.\n\nLos no ciudadanos solo pueden beneficiarse de las recompensas HEZ. diff --git a/common/src/main/res/values-fr-rFR/strings.xml b/common/src/main/res/values-fr-rFR/strings.xml index d664719..272d33d 100644 --- a/common/src/main/res/values-fr-rFR/strings.xml +++ b/common/src/main/res/values-fr-rFR/strings.xml @@ -2029,6 +2029,7 @@ Pont DOT ↔ HEZ + Pont USDT(DOT) ↔ USDT(HEZ) Pont DOT ↔ HEZ Vous envoyez Vous recevez (estimé) @@ -2048,4 +2049,5 @@ Pezkuwi Score de confiance Demande et Actions + Utilisez notre Telegram MiniApp pour les services de citoyenneté du Kurdistan numérique.\n\nPour gagner des récompenses PEZ, vous devez détenir un ticket Welatî et avoir staké au moins 10 HEZ.\n\nLes non-citoyens ne peuvent bénéficier que des récompenses HEZ. diff --git a/common/src/main/res/values-hu/strings.xml b/common/src/main/res/values-hu/strings.xml index 1ad5874..af8fd87 100644 --- a/common/src/main/res/values-hu/strings.xml +++ b/common/src/main/res/values-hu/strings.xml @@ -2029,6 +2029,7 @@ DOT ↔ HEZ híd + USDT(DOT) ↔ USDT(HEZ) híd DOT ↔ HEZ híd Küldöd Kapod (becsült) diff --git a/common/src/main/res/values-in/strings.xml b/common/src/main/res/values-in/strings.xml index ab7c44f..bb96f79 100644 --- a/common/src/main/res/values-in/strings.xml +++ b/common/src/main/res/values-in/strings.xml @@ -2015,6 +2015,7 @@ Jembatan DOT ↔ HEZ + Jembatan USDT(DOT) ↔ USDT(HEZ) Jembatan DOT ↔ HEZ Anda kirim Anda terima (perkiraan) @@ -2034,4 +2035,5 @@ Pezkuwi Skor Kepercayaan Ajukan & Tindakan + Gunakan Telegram MiniApp kami untuk layanan kewarganegaraan Kurdistan Digital.\n\nUntuk mendapatkan hadiah PEZ, Anda harus memiliki tiket Welatî dan telah staking minimal 10 HEZ.\n\nNon-warga negara hanya dapat memperoleh manfaat dari hadiah HEZ. diff --git a/common/src/main/res/values-it/strings.xml b/common/src/main/res/values-it/strings.xml index 61505e8..832b341 100644 --- a/common/src/main/res/values-it/strings.xml +++ b/common/src/main/res/values-it/strings.xml @@ -2029,6 +2029,7 @@ Ponte DOT ↔ HEZ + Ponte USDT(DOT) ↔ USDT(HEZ) Ponte DOT ↔ HEZ Invii Ricevi (stimato) diff --git a/common/src/main/res/values-ja/strings.xml b/common/src/main/res/values-ja/strings.xml index 0f861f3..1ca6577 100644 --- a/common/src/main/res/values-ja/strings.xml +++ b/common/src/main/res/values-ja/strings.xml @@ -2015,6 +2015,7 @@ DOT ↔ HEZ ブリッジ + USDT(DOT) ↔ USDT(HEZ) ブリッジ DOT ↔ HEZ ブリッジ 送金額 受取額(概算) @@ -2034,4 +2035,5 @@ Pezkuwi 信頼スコア 申請とアクション + デジタルクルディスタンの市民サービスにはTelegram MiniAppをご利用ください。\n\nPEZ報酬を獲得するには、Welatîチケットを保有し、最低10 HEZをステーキングする必要があります。\n\n非市民はHEZ報酬のみを受け取ることができます。 diff --git a/common/src/main/res/values-ko/strings.xml b/common/src/main/res/values-ko/strings.xml index 830ae8a..badc3bf 100644 --- a/common/src/main/res/values-ko/strings.xml +++ b/common/src/main/res/values-ko/strings.xml @@ -2015,6 +2015,7 @@ DOT ↔ HEZ 브릿지 + USDT(DOT) ↔ USDT(HEZ) 브릿지 DOT ↔ HEZ 브릿지 보내는 금액 받는 금액 (예상) @@ -2034,4 +2035,5 @@ Pezkuwi 신뢰 점수 신청 및 작업 + 디지털 쿠르디스탄 시민권 서비스를 위해 Telegram MiniApp을 사용하세요.\n\nPEZ 보상을 받으려면 Welatî 티켓을 보유하고 최소 10 HEZ를 스테이킹해야 합니다.\n\n비시민권자는 HEZ 보상만 받을 수 있습니다. diff --git a/common/src/main/res/values-ku/strings.xml b/common/src/main/res/values-ku/strings.xml index 7601027..b6e954f 100644 --- a/common/src/main/res/values-ku/strings.xml +++ b/common/src/main/res/values-ku/strings.xml @@ -2742,6 +2742,7 @@ Pira DOT ↔ HEZ + Pira USDT(DOT) ↔ USDT(HEZ) Pira DOT ↔ HEZ Tu dişînî Tu distînî (texmîn) @@ -2761,4 +2762,5 @@ Pezkuwi Pûana Pêbaweriyê Serlêdan û Karên + Ji bo karên hemwelatîbûna Kurdistana Dîjîtal MiniApp\'a me ya Telegram bikar bînin.\n\nJi bo qezenckirina xelatên PEZ, divê hûn xwediyê bilêta Welatî bin û herî kêm 10 HEZ stake kiribe bin.\n\nKesên ne-hemwelatî tenê dikarin ji xelatên HEZ sûd werbigirin. diff --git a/common/src/main/res/values-pl/strings.xml b/common/src/main/res/values-pl/strings.xml index 7f25084..200ff3d 100644 --- a/common/src/main/res/values-pl/strings.xml +++ b/common/src/main/res/values-pl/strings.xml @@ -2057,6 +2057,7 @@ Most DOT ↔ HEZ + Most USDT(DOT) ↔ USDT(HEZ) Most DOT ↔ HEZ Wysyłasz Otrzymasz (szacunkowo) diff --git a/common/src/main/res/values-pt/strings.xml b/common/src/main/res/values-pt/strings.xml index d879c2a..177aea6 100644 --- a/common/src/main/res/values-pt/strings.xml +++ b/common/src/main/res/values-pt/strings.xml @@ -2029,6 +2029,7 @@ Ponte DOT ↔ HEZ + Ponte USDT(DOT) ↔ USDT(HEZ) Ponte DOT ↔ HEZ Você envia Você recebe (estimado) @@ -2048,4 +2049,5 @@ Pezkuwi Pontuação de confiança Candidatura e Ações + Use nosso Telegram MiniApp para serviços de cidadania do Curdistão Digital.\n\nPara ganhar recompensas PEZ, você deve possuir um bilhete Welatî e ter pelo menos 10 HEZ em stake.\n\nNão-cidadãos só podem se beneficiar das recompensas HEZ. diff --git a/common/src/main/res/values-ru/strings.xml b/common/src/main/res/values-ru/strings.xml index dc9b224..15ca536 100644 --- a/common/src/main/res/values-ru/strings.xml +++ b/common/src/main/res/values-ru/strings.xml @@ -2057,6 +2057,7 @@ Мост DOT ↔ HEZ + Мост USDT(DOT) ↔ USDT(HEZ) Мост DOT ↔ HEZ Вы отправляете Вы получите (примерно) @@ -2076,4 +2077,5 @@ Pezkuwi Рейтинг доверия Заявка и Действия + Используйте наше Telegram MiniApp для услуг цифрового гражданства Курдистана.\n\nДля получения наград PEZ необходимо иметь билет Welatî и застейкать минимум 10 HEZ.\n\nНе-граждане могут получать только награды HEZ. diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index e92ab9d..da7c7a5 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -3,4 +3,5 @@ Pezkuwi Güven Puanı Başvuru ve İşlemler + Dijital Kurdistan vatandaşlık işlemleri için Telegram MiniApp\'imizi kullanın.\n\nPEZ ödülleri kazanmak için Welatî tikesi sahibi olmanız ve en az 10 HEZ stake etmiş olmanız gereklidir.\n\nVatandaş olmayanlar yalnızca HEZ ödüllerinden yararlanabilir. diff --git a/common/src/main/res/values-vi/strings.xml b/common/src/main/res/values-vi/strings.xml index 0365235..6ec51f7 100644 --- a/common/src/main/res/values-vi/strings.xml +++ b/common/src/main/res/values-vi/strings.xml @@ -2015,6 +2015,7 @@ Cầu nối DOT ↔ HEZ + Cầu nối USDT(DOT) ↔ USDT(HEZ) Cầu nối DOT ↔ HEZ Bạn gửi Bạn nhận (ước tính) @@ -2034,4 +2035,5 @@ Pezkuwi Điểm tin cậy Đăng ký & Hành động + Sử dụng Telegram MiniApp của chúng tôi cho dịch vụ công dân Kurdistan kỹ thuật số.\n\nĐể nhận phần thưởng PEZ, bạn phải sở hữu vé Welatî và đã stake ít nhất 10 HEZ.\n\nNgười không phải công dân chỉ có thể hưởng lợi từ phần thưởng HEZ. diff --git a/common/src/main/res/values-zh-rCN/strings.xml b/common/src/main/res/values-zh-rCN/strings.xml index caa278c..5dd2e95 100644 --- a/common/src/main/res/values-zh-rCN/strings.xml +++ b/common/src/main/res/values-zh-rCN/strings.xml @@ -2015,6 +2015,7 @@ DOT ↔ HEZ 跨链桥 + USDT(DOT) ↔ USDT(HEZ) 跨链桥 DOT ↔ HEZ 跨链桥 发送 接收(预计) @@ -2034,4 +2035,5 @@ Pezkuwi 信任评分 申请与操作 + 使用我们的 Telegram MiniApp 进行数字库尔德斯坦公民服务。\n\n要获得 PEZ 奖励,您必须持有 Welatî 票并至少质押 10 HEZ。\n\n非公民只能从 HEZ 奖励中受益。 diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index a99ef59..df93e79 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -351,6 +351,7 @@ Sell tokens Buy tokens Bridge DOT ↔ HEZ + Bridge USDT(DOT) ↔ USDT(HEZ) DOT ↔ HEZ Bridge @@ -368,6 +369,7 @@ Enter amount HEZ→DOT swaps may have limited availability based on current liquidity. HEZ→DOT swaps are temporarily unavailable. Please try again when DOT liquidity is sufficient. + USDT(Pez)→USDT(Pol) swaps are temporarily unavailable. Waiting for 1:1 liquidity to be established. Buy/Sell @@ -2763,4 +2765,5 @@ Pezkuwi Trust Score Apply & Actions + Use our Telegram MiniApp for Digital Kurdistan citizenship services.\n\nTo earn PEZ rewards, you must hold a Welatî ticket and stake at least 10 HEZ.\n\nNon-citizens can only benefit from HEZ rewards. diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt index 9a89403..8eabef3 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt @@ -22,7 +22,7 @@ class PezkuwiDashboardRepository( val trustScore = queryTrustScore(chainId, accountId) return PezkuwiDashboardData( - roles = roles, + roles = roles.ifEmpty { listOf("Non-Citizen") }, trustScore = trustScore ) } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/common/buySell/BuySellSelectorMixin.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/common/buySell/BuySellSelectorMixin.kt index 3bbbe9a..3438c15 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/common/buySell/BuySellSelectorMixin.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/common/buySell/BuySellSelectorMixin.kt @@ -75,7 +75,8 @@ class RealBuySellSelectorMixin( private suspend fun openAllAssetsSelector() = BuySellSelectorMixin.SelectorPayload( buyItem(enabled = true) { router.openBuyFlow() }, sellItem(enabled = buySellRestrictionCheckMixin.isAllowed()) { router.openSellFlow() }, - bridgeItem(enabled = true) { router.openBridgeFlow() } + bridgeItem(enabled = true) { router.openBridgeFlow() }, + bridgeUsdtItem(enabled = true) { router.openBridgeFlow() } ) private suspend fun openSpecifiedAssetSelector(selectorType: SelectorType.Asset): BuySellSelectorMixin.SelectorPayload? { @@ -125,6 +126,16 @@ class RealBuySellSelectorMixin( ) } + private fun bridgeUsdtItem(enabled: Boolean, action: () -> Unit): ListSelectorMixin.Item { + return ListSelectorMixin.Item( + R.drawable.ic_bridge, + if (enabled) R.color.icon_primary else R.color.icon_inactive, + R.string.wallet_asset_bridge_usdt, + if (enabled) R.color.text_primary else R.color.button_text_inactive, + action + ) + } + private fun sellErrorAction(): () -> Unit = { coroutineScope.launch { buySellRestrictionCheckMixin.checkRestrictionAndDo { diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeFragment.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeFragment.kt index a9b9095..a694cbc 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeFragment.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeFragment.kt @@ -18,13 +18,22 @@ class BridgeFragment : BaseFragment() { override fun initViews() { binder.bridgeToolbar.setHomeButtonListener { viewModel.backClicked() } - // Direction toggle - binder.bridgeDirectionDotToHez.setOnClickListener { - viewModel.setDirection(BridgeDirection.DOT_TO_HEZ) + // Pair selector + binder.bridgePairDotHez.setOnClickListener { + viewModel.setPair(BridgePair.DOT_HEZ) } - binder.bridgeDirectionHezToDot.setOnClickListener { - viewModel.setDirection(BridgeDirection.HEZ_TO_DOT) + binder.bridgePairUsdt.setOnClickListener { + viewModel.setPair(BridgePair.USDT) + } + + // Direction toggle + binder.bridgeDirectionLeft.setOnClickListener { + viewModel.setDirectionLeft() + } + + binder.bridgeDirectionRight.setOnClickListener { + viewModel.setDirectionRight() } // Amount input @@ -54,6 +63,10 @@ class BridgeFragment : BaseFragment() { } override fun subscribe(viewModel: BridgeViewModel) { + viewModel.pair.observe { pair -> + updatePairUI(pair) + } + viewModel.direction.observe { direction -> updateDirectionUI(direction) } @@ -74,11 +87,11 @@ class BridgeFragment : BaseFragment() { binder.bridgeSwapButton.setState(state) } - viewModel.showHezToDotWarning.observe { show -> + viewModel.showWarning.observe { show -> binder.bridgeHezToDotWarning.visibility = if (show) View.VISIBLE else View.GONE } - viewModel.hezToDotBlocked.observe { blocked -> + viewModel.warningBlocked.observe { blocked -> if (blocked) { binder.bridgeHezToDotWarning.setBackgroundColor(resources.getColor(R.color.error_block_background, null)) } else { @@ -86,40 +99,82 @@ class BridgeFragment : BaseFragment() { } } - viewModel.blockReason.observe { reason -> - if (reason.isNotEmpty()) { - binder.bridgeHezToDotWarning.text = reason - } else { - binder.bridgeHezToDotWarning.text = getString(R.string.bridge_hez_to_dot_warning) + viewModel.warningText.observe { text -> + if (text.isNotEmpty()) { + binder.bridgeHezToDotWarning.text = text + } + } + } + + private fun updatePairUI(pair: BridgePair) { + when (pair) { + BridgePair.DOT_HEZ -> { + binder.bridgePairDotHez.setBackgroundResource(R.drawable.bg_button_primary) + binder.bridgePairDotHez.setTextColor(resources.getColor(R.color.text_primary, null)) + binder.bridgePairUsdt.background = null + binder.bridgePairUsdt.setTextColor(resources.getColor(R.color.text_secondary, null)) + } + BridgePair.USDT -> { + binder.bridgePairUsdt.setBackgroundResource(R.drawable.bg_button_primary) + binder.bridgePairUsdt.setTextColor(resources.getColor(R.color.text_primary, null)) + binder.bridgePairDotHez.background = null + binder.bridgePairDotHez.setTextColor(resources.getColor(R.color.text_secondary, null)) } } } private fun updateDirectionUI(direction: BridgeDirection) { + val isLeft = direction == BridgeDirection.DOT_TO_HEZ || direction == BridgeDirection.USDT_TO_WUSDT + + if (isLeft) { + binder.bridgeDirectionLeft.setBackgroundResource(R.drawable.bg_button_primary) + binder.bridgeDirectionLeft.setTextColor(resources.getColor(R.color.text_primary, null)) + binder.bridgeDirectionRight.background = null + binder.bridgeDirectionRight.setTextColor(resources.getColor(R.color.text_secondary, null)) + } else { + binder.bridgeDirectionRight.setBackgroundResource(R.drawable.bg_button_primary) + binder.bridgeDirectionRight.setTextColor(resources.getColor(R.color.text_primary, null)) + binder.bridgeDirectionLeft.background = null + binder.bridgeDirectionLeft.setTextColor(resources.getColor(R.color.text_secondary, null)) + } + when (direction) { BridgeDirection.DOT_TO_HEZ -> { - binder.bridgeDirectionDotToHez.setBackgroundResource(R.drawable.bg_button_primary) - binder.bridgeDirectionDotToHez.setTextColor(resources.getColor(R.color.text_primary, null)) - binder.bridgeDirectionHezToDot.background = null - binder.bridgeDirectionHezToDot.setTextColor(resources.getColor(R.color.text_secondary, null)) - + binder.bridgeDirectionLeft.text = "DOT → HEZ" + binder.bridgeDirectionRight.text = "HEZ → DOT" binder.bridgeFromToken.text = "DOT" binder.bridgeToToken.text = "HEZ" } BridgeDirection.HEZ_TO_DOT -> { - binder.bridgeDirectionHezToDot.setBackgroundResource(R.drawable.bg_button_primary) - binder.bridgeDirectionHezToDot.setTextColor(resources.getColor(R.color.text_primary, null)) - binder.bridgeDirectionDotToHez.background = null - binder.bridgeDirectionDotToHez.setTextColor(resources.getColor(R.color.text_secondary, null)) - + binder.bridgeDirectionLeft.text = "DOT → HEZ" + binder.bridgeDirectionRight.text = "HEZ → DOT" binder.bridgeFromToken.text = "HEZ" binder.bridgeToToken.text = "DOT" } + BridgeDirection.USDT_TO_WUSDT -> { + binder.bridgeDirectionLeft.text = "USDT(Pol) → USDT(Pez)" + binder.bridgeDirectionRight.text = "USDT(Pez) → USDT(Pol)" + binder.bridgeFromToken.text = "USDT" + binder.bridgeToToken.text = "USDT" + } + BridgeDirection.WUSDT_TO_USDT -> { + binder.bridgeDirectionLeft.text = "USDT(Pol) → USDT(Pez)" + binder.bridgeDirectionRight.text = "USDT(Pez) → USDT(Pol)" + binder.bridgeFromToken.text = "USDT" + binder.bridgeToToken.text = "USDT" + } } } } +enum class BridgePair { + DOT_HEZ, + USDT +} + enum class BridgeDirection { DOT_TO_HEZ, - HEZ_TO_DOT + HEZ_TO_DOT, + USDT_TO_WUSDT, + WUSDT_TO_USDT } diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeViewModel.kt index b37de7c..3575ffd 100644 --- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeViewModel.kt +++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/bridge/BridgeViewModel.kt @@ -28,34 +28,30 @@ class BridgeViewModel( ) : BaseViewModel() { companion object { - // Bridge wallet account ID (derived from seed, same on all chains) - // Address: 5C5CW7xDmiXtCgfUCbKFF4ViJuCJJQpDZqWQ1mSTjehGzE3p (generic format) private const val BRIDGE_ADDRESS_GENERIC = "5C5CW7xDmiXtCgfUCbKFF4ViJuCJJQpDZqWQ1mSTjehGzE3p" - // Chain IDs val POLKADOT_ASSET_HUB_ID = ChainGeneses.POLKADOT_ASSET_HUB val PEZKUWI_ASSET_HUB_ID = ChainGeneses.PEZKUWI_ASSET_HUB - // Utility asset ID (native token) const val UTILITY_ASSET_ID = 0 - // Fallback rate: 1 DOT = 3 HEZ (only if CoinGecko unavailable) + // USDT asset IDs in chain config + const val POLKADOT_USDT_ASSET_ID = 1 // assetId in chains.json for Polkadot AH + const val PEZKUWI_USDT_ASSET_ID = 1000 // assetId in chains.json for Pezkuwi AH + const val FALLBACK_RATE = 3.0 - - // Fee: 0.1% const val FEE_PERCENT = 0.001 - - // Minimums const val MIN_DOT = 0.1 const val MIN_HEZ = 0.3 + const val MIN_USDT = 1.0 - // CoinGecko API const val COINGECKO_API = "https://api.coingecko.com/api/v3/simple/price?ids=polkadot,hezkurd&vs_currencies=usd" - - // Bridge Status API const val BRIDGE_STATUS_API = "http://217.77.6.126:3030/status" } + private val _pair = MutableLiveData(BridgePair.DOT_HEZ) + val pair: LiveData = _pair + private val _direction = MutableLiveData(BridgeDirection.DOT_TO_HEZ) val direction: LiveData = _direction @@ -71,122 +67,64 @@ class BridgeViewModel( private val _buttonState = MutableLiveData() val buttonState: LiveData = _buttonState - private val _showHezToDotWarning = MutableLiveData(false) - val showHezToDotWarning: LiveData = _showHezToDotWarning + private val _showWarning = MutableLiveData(false) + val showWarning: LiveData = _showWarning - private val _hezToDotBlocked = MutableLiveData(false) - val hezToDotBlocked: LiveData = _hezToDotBlocked + private val _warningBlocked = MutableLiveData(false) + val warningBlocked: LiveData = _warningBlocked - private val _blockReason = MutableLiveData() - val blockReason: LiveData = _blockReason - - private val _rateSource = MutableLiveData() - val rateSource: LiveData = _rateSource + private val _warningText = MutableLiveData() + val warningText: LiveData = _warningText private var currentAmount: Double = 0.0 private var dotToHezRate: Double = FALLBACK_RATE - private var isUsingFallback: Boolean = true private var isHezToDotActive: Boolean = false + private var isWusdtToUsdtActive: Boolean = false init { fetchExchangeRate() fetchBridgeStatus() } - private fun fetchExchangeRate() { - launch { - try { - val (rate, source) = withContext(Dispatchers.IO) { - fetchRateFromCoinGecko() - } - dotToHezRate = rate - isUsingFallback = source == "fallback" - _rateSource.postValue(source) - updateUI() - calculateOutput() - } catch (e: Exception) { - // Use fallback - dotToHezRate = FALLBACK_RATE - isUsingFallback = true - _rateSource.postValue("fallback") - updateUI() + fun setPair(newPair: BridgePair) { + if (_pair.value != newPair) { + _pair.value = newPair + // Reset direction to left (forward) when switching pair + _direction.value = when (newPair) { + BridgePair.DOT_HEZ -> BridgeDirection.DOT_TO_HEZ + BridgePair.USDT -> BridgeDirection.USDT_TO_WUSDT } - } - } - - private fun fetchRateFromCoinGecko(): Pair { - return try { - val response = URL(COINGECKO_API).readText() - val json = JSONObject(response) - - val dotPrice = json.optJSONObject("polkadot")?.optDouble("usd", 0.0) ?: 0.0 - val hezPrice = json.optJSONObject("hezkurd")?.optDouble("usd", 0.0) ?: 0.0 - - when { - dotPrice > 0 && hezPrice > 0 -> { - // Both prices available - calculate real rate - val rate = dotPrice / hezPrice - Pair(rate, "coingecko") - } - dotPrice > 0 -> { - // Only DOT price - use fallback for HEZ (1 DOT = 3 HEZ means HEZ = DOT/3) - Pair(FALLBACK_RATE, "coingecko+fallback") - } - else -> { - // No prices - use pure fallback - Pair(FALLBACK_RATE, "fallback") - } - } - } catch (e: Exception) { - Pair(FALLBACK_RATE, "fallback") - } - } - - private fun fetchBridgeStatus() { - launch { - try { - val active = withContext(Dispatchers.IO) { - fetchHezToDotStatus() - } - isHezToDotActive = active - updateHezToDotState() - } catch (e: Exception) { - // If API unavailable, assume not active for safety - isHezToDotActive = false - updateHezToDotState() - } - } - } - - private fun fetchHezToDotStatus(): Boolean { - return try { - val response = URL(BRIDGE_STATUS_API).readText() - val json = JSONObject(response) - json.optBoolean("hezToDotActive", false) - } catch (e: Exception) { - false - } - } - - private fun updateHezToDotState() { - val dir = _direction.value ?: return - if (dir == BridgeDirection.HEZ_TO_DOT && !isHezToDotActive) { - _hezToDotBlocked.postValue(true) - _blockReason.postValue(resourceManager.getString(R.string.bridge_hez_to_dot_blocked)) - } else { - _hezToDotBlocked.postValue(false) - _blockReason.postValue("") - } - updateButtonState() - } - - fun setDirection(newDirection: BridgeDirection) { - if (_direction.value != newDirection) { - _direction.value = newDirection - _showHezToDotWarning.value = newDirection == BridgeDirection.HEZ_TO_DOT - updateHezToDotState() updateUI() calculateOutput() + updateWarningState() + } + } + + fun setDirectionLeft() { + val newDir = when (_pair.value) { + BridgePair.DOT_HEZ -> BridgeDirection.DOT_TO_HEZ + BridgePair.USDT -> BridgeDirection.USDT_TO_WUSDT + null -> BridgeDirection.DOT_TO_HEZ + } + if (_direction.value != newDir) { + _direction.value = newDir + updateUI() + calculateOutput() + updateWarningState() + } + } + + fun setDirectionRight() { + val newDir = when (_pair.value) { + BridgePair.DOT_HEZ -> BridgeDirection.HEZ_TO_DOT + BridgePair.USDT -> BridgeDirection.WUSDT_TO_USDT + null -> BridgeDirection.HEZ_TO_DOT + } + if (_direction.value != newDir) { + _direction.value = newDir + updateUI() + calculateOutput() + updateWarningState() } } @@ -201,26 +139,27 @@ class BridgeViewModel( if (currentAmount <= 0) return launch { - // Determine which chain and asset to send from based on direction val chainId = when (dir) { - BridgeDirection.DOT_TO_HEZ -> POLKADOT_ASSET_HUB_ID // Send DOT from Polkadot Asset Hub - BridgeDirection.HEZ_TO_DOT -> PEZKUWI_ASSET_HUB_ID // Send HEZ from Pezkuwi Asset Hub + BridgeDirection.DOT_TO_HEZ -> POLKADOT_ASSET_HUB_ID + BridgeDirection.HEZ_TO_DOT -> PEZKUWI_ASSET_HUB_ID + BridgeDirection.USDT_TO_WUSDT -> POLKADOT_ASSET_HUB_ID + BridgeDirection.WUSDT_TO_USDT -> PEZKUWI_ASSET_HUB_ID } - // Get the chain to convert address to correct format - val chain = chainRegistry.getChain(chainId) + val assetId = when (dir) { + BridgeDirection.DOT_TO_HEZ -> UTILITY_ASSET_ID + BridgeDirection.HEZ_TO_DOT -> UTILITY_ASSET_ID + BridgeDirection.USDT_TO_WUSDT -> POLKADOT_USDT_ASSET_ID + BridgeDirection.WUSDT_TO_USDT -> PEZKUWI_USDT_ASSET_ID + } - // Convert generic address to chain-specific format + val chain = chainRegistry.getChain(chainId) val accountId = BRIDGE_ADDRESS_GENERIC.toAccountId() val bridgeAddress = chain.addressOf(accountId) - // Create asset payload (utility asset = native token) - val assetPayload = AssetPayload(chainId, UTILITY_ASSET_ID) - - // Create send payload specifying the origin asset + val assetPayload = AssetPayload(chainId, assetId) val sendPayload = SendPayload.SpecifiedOrigin(assetPayload) - // Open send screen with pre-filled bridge address AND amount router.openSend(sendPayload, bridgeAddress, currentAmount) } } @@ -229,15 +168,108 @@ class BridgeViewModel( router.back() } + private fun fetchExchangeRate() { + launch { + try { + val (rate, _) = withContext(Dispatchers.IO) { + fetchRateFromCoinGecko() + } + dotToHezRate = rate + updateUI() + calculateOutput() + } catch (e: Exception) { + dotToHezRate = FALLBACK_RATE + updateUI() + } + } + } + + private fun fetchRateFromCoinGecko(): Pair { + return try { + val response = URL(COINGECKO_API).readText() + val json = JSONObject(response) + val dotPrice = json.optJSONObject("polkadot")?.optDouble("usd", 0.0) ?: 0.0 + val hezPrice = json.optJSONObject("hezkurd")?.optDouble("usd", 0.0) ?: 0.0 + when { + dotPrice > 0 && hezPrice > 0 -> Pair(dotPrice / hezPrice, "coingecko") + else -> Pair(FALLBACK_RATE, "fallback") + } + } catch (e: Exception) { + Pair(FALLBACK_RATE, "fallback") + } + } + + private fun fetchBridgeStatus() { + launch { + try { + val (hezToDot, wusdtToUsdt) = withContext(Dispatchers.IO) { + fetchStatusFromApi() + } + isHezToDotActive = hezToDot + isWusdtToUsdtActive = wusdtToUsdt + updateWarningState() + } catch (e: Exception) { + isHezToDotActive = false + isWusdtToUsdtActive = false + updateWarningState() + } + } + } + + private fun fetchStatusFromApi(): Pair { + return try { + val response = URL(BRIDGE_STATUS_API).readText() + val json = JSONObject(response) + val hezToDot = json.optBoolean("hezToDotActive", false) + val wusdtToUsdt = json.optBoolean("wusdtToUsdtActive", false) + Pair(hezToDot, wusdtToUsdt) + } catch (e: Exception) { + Pair(false, false) + } + } + + private fun updateWarningState() { + val dir = _direction.value ?: return + + when (dir) { + BridgeDirection.HEZ_TO_DOT -> { + _showWarning.postValue(true) + if (!isHezToDotActive) { + _warningBlocked.postValue(true) + _warningText.postValue(resourceManager.getString(R.string.bridge_hez_to_dot_blocked)) + } else { + _warningBlocked.postValue(false) + _warningText.postValue(resourceManager.getString(R.string.bridge_hez_to_dot_warning)) + } + } + BridgeDirection.WUSDT_TO_USDT -> { + _showWarning.postValue(true) + if (!isWusdtToUsdtActive) { + _warningBlocked.postValue(true) + _warningText.postValue(resourceManager.getString(R.string.bridge_wusdt_to_usdt_blocked)) + } else { + _warningBlocked.postValue(false) + _warningText.postValue("") + _showWarning.postValue(false) + } + } + else -> { + _showWarning.postValue(false) + } + } + updateButtonState() + } + private fun calculateOutput() { val dir = _direction.value ?: return val grossOutput = when (dir) { BridgeDirection.DOT_TO_HEZ -> currentAmount * dotToHezRate BridgeDirection.HEZ_TO_DOT -> currentAmount / dotToHezRate + BridgeDirection.USDT_TO_WUSDT -> currentAmount // 1:1 + BridgeDirection.WUSDT_TO_USDT -> currentAmount // 1:1 } - // Apply fee val netOutput = grossOutput * (1 - FEE_PERCENT) _outputAmount.value = if (netOutput > 0) { @@ -250,18 +282,21 @@ class BridgeViewModel( private fun updateUI() { val dir = _direction.value ?: return - val rateFormatted = BigDecimal(dotToHezRate).setScale(4, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString() - val reverseRateFormatted = BigDecimal(1.0 / dotToHezRate).setScale(6, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString() - when (dir) { BridgeDirection.DOT_TO_HEZ -> { + val rateFormatted = BigDecimal(dotToHezRate).setScale(4, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString() _exchangeRateText.value = "1 DOT = $rateFormatted HEZ" _minimumText.value = "$MIN_DOT DOT" } BridgeDirection.HEZ_TO_DOT -> { - _exchangeRateText.value = "1 HEZ = $reverseRateFormatted DOT" + val reverseRate = BigDecimal(1.0 / dotToHezRate).setScale(6, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString() + _exchangeRateText.value = "1 HEZ = $reverseRate DOT" _minimumText.value = "$MIN_HEZ HEZ" } + BridgeDirection.USDT_TO_WUSDT, BridgeDirection.WUSDT_TO_USDT -> { + _exchangeRateText.value = "1:1 (fee 0.1%)" + _minimumText.value = "$MIN_USDT USDT" + } } updateButtonState() @@ -272,12 +307,14 @@ class BridgeViewModel( val minimum = when (dir) { BridgeDirection.DOT_TO_HEZ -> MIN_DOT BridgeDirection.HEZ_TO_DOT -> MIN_HEZ + BridgeDirection.USDT_TO_WUSDT, BridgeDirection.WUSDT_TO_USDT -> MIN_USDT } _buttonState.value = when { currentAmount <= 0 -> ButtonState.DISABLED currentAmount < minimum -> ButtonState.DISABLED dir == BridgeDirection.HEZ_TO_DOT && !isHezToDotActive -> ButtonState.DISABLED + dir == BridgeDirection.WUSDT_TO_USDT && !isWusdtToUsdtActive -> ButtonState.DISABLED else -> ButtonState.NORMAL } } diff --git a/feature-assets/src/main/res/layout/fragment_bridge.xml b/feature-assets/src/main/res/layout/fragment_bridge.xml index 32a219f..4bf845b 100644 --- a/feature-assets/src/main/res/layout/fragment_bridge.xml +++ b/feature-assets/src/main/res/layout/fragment_bridge.xml @@ -25,6 +25,41 @@ android:orientation="vertical" android:padding="16dp"> + + + + + + + + + - - - - + + android:orientation="horizontal" + android:gravity="center_vertical"> - + android:layout_weight="1" + android:orientation="vertical"> - + + + + + + + + + + + + + + + + + + diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt index 52a0998..54e0978 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/BagListRepository.kt @@ -38,24 +38,19 @@ suspend fun BagListRepository.bagListLocatorOrThrow(chainId: ChainId): BagListLo class LocalBagListRepository( private val localStorage: StorageDataSource, - private val remoteStorage: StorageDataSource, private val chainRegistry: ChainRegistry ) : BagListRepository { override suspend fun bagThresholds(chainId: ChainId): List? { - return runCatching { - chainRegistry.withRuntime(chainId) { - runtime.metadata.voterListOrNull()?.constant("BagThresholds")?.getAs(collectionOf(::score)) - } - }.getOrNull() + return chainRegistry.withRuntime(chainId) { + runtime.metadata.voterListOrNull()?.constant("BagThresholds")?.getAs(collectionOf(::score)) + } } override suspend fun bagListSize(chainId: ChainId): BigInteger? { - return runCatching { - remoteStorage.query(chainId) { - runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber) - } - }.getOrNull() + return localStorage.query(chainId) { + runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber) + } } override suspend fun maxElectingVotes(chainId: ChainId): BigInteger? { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/ParasRepository.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/ParasRepository.kt index 6d6577f..48df5cc 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/ParasRepository.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/ParasRepository.kt @@ -6,7 +6,7 @@ import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber import io.novafoundation.nova.common.utils.parasOrNull import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId import io.novafoundation.nova.runtime.storage.source.StorageDataSource -import io.novasama.substrate_sdk_android.runtime.metadata.storageOrNull +import io.novasama.substrate_sdk_android.runtime.metadata.storage interface ParasRepository { @@ -21,7 +21,7 @@ class RealParasRepository( override suspend fun activePublicParachains(chainId: ChainId): Int? { return localSource.query(chainId) { - val parachains = runtime.metadata.parasOrNull()?.storageOrNull("Parachains") + val parachains = runtime.metadata.parasOrNull()?.storage("Parachains") ?.query(binding = ::bindParachains) ?: return@query null parachains.count { it >= LOWEST_PUBLIC_ID } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt index 1c04de3..1a0dc29 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/data/repository/StakingRepositoryImpl.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.common.utils.metadata import io.novafoundation.nova.common.utils.numberConstant import io.novafoundation.nova.common.utils.numberConstantOrNull import io.novafoundation.nova.common.utils.staking +import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core_db.dao.AccountStakingDao import io.novafoundation.nova.core_db.model.AccountStakingLocal import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap @@ -49,6 +50,7 @@ import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindin import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindSlashDeferDuration import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindSlashingSpans import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.bindings.bindValidatorPrefs +import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.ValidatorExposureUpdater import io.novafoundation.nova.feature_staking_impl.data.network.blockhain.updaters.activeEraStorageKey import io.novafoundation.nova.feature_wallet_api.domain.interfaces.WalletConstants import io.novafoundation.nova.runtime.call.MultiChainRuntimeCallsApi @@ -86,6 +88,7 @@ class StakingRepositoryImpl( private val localStorage: StorageDataSource, private val walletConstants: WalletConstants, private val chainRegistry: ChainRegistry, + private val storageCache: StorageCache, private val multiChainRuntimeCallsApi: MultiChainRuntimeCallsApi, ) : StakingRepository { @@ -124,7 +127,7 @@ class StakingRepositoryImpl( return runtime.metadata.staking().numberConstant("SessionsPerEra", runtime) // How many sessions per era } - override suspend fun getActiveEraIndex(chainId: ChainId): EraIndex = remoteStorage.query(chainId) { + override suspend fun getActiveEraIndex(chainId: ChainId): EraIndex = localStorage.query(chainId) { metadata.staking.activeEra.queryNonNull() } @@ -161,7 +164,7 @@ class StakingRepositoryImpl( } } - private suspend fun fetchPagedEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap = remoteStorage.query(chainId) { + private suspend fun fetchPagedEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap = localStorage.query(chainId) { val eraStakersOverview = metadata.staking().storage("ErasStakersOverview").entries( eraIndex, keyExtractor = { (_: BigInteger, accountId: ByteArray) -> accountId.toHexString() }, @@ -204,7 +207,7 @@ class StakingRepositoryImpl( } } - private suspend fun fetchLegacyEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap = remoteStorage.query(chainId) { + private suspend fun fetchLegacyEraStakers(chainId: ChainId, eraIndex: EraIndex): AccountIdMap = localStorage.query(chainId) { runtime.metadata.staking().storage("ErasStakers").entries( eraIndex, keyExtractor = { (_: BigInteger, accountId: ByteArray) -> accountId.toHexString() }, @@ -346,7 +349,7 @@ class StakingRepositoryImpl( val runtime = runtimeFor(chainId) return runtime.metadata.staking().storageOrNull(storageName)?.let { storageEntry -> - remoteStorage.query( + localStorage.query( keyBuilder = { storageEntry.storageKey() }, binding = { scale, _ -> scale?.let { binder(scale, runtime, storageEntry.returnType()) } }, chainId = chainId @@ -402,8 +405,9 @@ class StakingRepositoryImpl( } private suspend fun isPagedExposuresUsed(chainId: ChainId): Boolean { - val runtime = runtimeFor(chainId) - return runtime.metadata.staking().storageOrNull("ErasStakersOverview") != null + val isPagedExposuresValue = storageCache.getEntry(ValidatorExposureUpdater.STORAGE_KEY_PAGED_EXPOSURES, chainId) + + return ValidatorExposureUpdater.decodeIsPagedExposuresValue(isPagedExposuresValue.content) } private fun observeAccountValidatorPrefs(chainId: ChainId, stashId: AccountId): Flow { 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 67c7775..d025836 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 @@ -11,6 +11,7 @@ import io.novafoundation.nova.common.data.network.rpc.BulkRetriever import io.novafoundation.nova.common.di.scope.FeatureScope import io.novafoundation.nova.common.presentation.AssetIconProvider import io.novafoundation.nova.common.resources.ResourceManager +import io.novafoundation.nova.core.storage.StorageCache import io.novafoundation.nova.core_db.dao.AccountStakingDao import io.novafoundation.nova.core_db.dao.ExternalBalanceDao import io.novafoundation.nova.core_db.dao.StakingRewardPeriodDao @@ -189,6 +190,7 @@ class StakingFeatureModule { @Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource, walletConstants: WalletConstants, chainRegistry: ChainRegistry, + storageCache: StorageCache, multiChainRuntimeCallsApi: MultiChainRuntimeCallsApi ): StakingRepository = StakingRepositoryImpl( accountStakingDao = accountStakingDao, @@ -196,6 +198,7 @@ class StakingFeatureModule { localStorage = localStorageSource, walletConstants = walletConstants, chainRegistry = chainRegistry, + storageCache = storageCache, multiChainRuntimeCallsApi = multiChainRuntimeCallsApi ) @@ -225,9 +228,8 @@ class StakingFeatureModule { @FeatureScope fun provideBagListRepository( @Named(LOCAL_STORAGE_SOURCE) localStorageSource: StorageDataSource, - @Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource, chainRegistry: ChainRegistry - ): BagListRepository = LocalBagListRepository(localStorageSource, remoteStorageSource, chainRegistry) + ): BagListRepository = LocalBagListRepository(localStorageSource, chainRegistry) @Provides @FeatureScope diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/nominationPool/NominationPoolModule.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/nominationPool/NominationPoolModule.kt index 3acf5bb..36fec3c 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/nominationPool/NominationPoolModule.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/di/staking/nominationPool/NominationPoolModule.kt @@ -26,7 +26,6 @@ import io.novafoundation.nova.feature_staking_impl.data.nominationPools.reposito import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.RealNominationPoolMembersRepository import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.RealNominationPoolStateRepository import io.novafoundation.nova.feature_staking_impl.data.nominationPools.repository.RealNominationPoolUnbondRepository -import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_impl.data.repository.StakingConstantsRepository import io.novafoundation.nova.feature_staking_impl.data.repository.StakingRewardsRepository import io.novafoundation.nova.feature_staking_impl.domain.StakingInteractor @@ -187,12 +186,10 @@ class NominationPoolModule { fun provideNominationPoolRewardCalculatorFactory( stakingSharedComputation: StakingSharedComputation, nominationPoolSharedComputation: NominationPoolSharedComputation, - stakingRepository: StakingRepository, ): NominationPoolRewardCalculatorFactory { return NominationPoolRewardCalculatorFactory( sharedStakingSharedComputation = stakingSharedComputation, - nominationPoolSharedComputation = nominationPoolSharedComputation, - stakingRepository = stakingRepository + nominationPoolSharedComputation = nominationPoolSharedComputation ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/StakingSharedComputation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/StakingSharedComputation.kt index 7a47d72..d17ad39 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/StakingSharedComputation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/common/StakingSharedComputation.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.transformLatest @@ -62,12 +61,7 @@ class StakingSharedComputation( val key = "ACTIVE_ERA:$chainId" return computationalCache.useSharedFlow(key, scope) { - flow { - val era = stakingRepository.getActiveEraIndex(chainId) - emit(era) - - emitAll(stakingRepository.observeActiveEraIndex(chainId)) - } + stakingRepository.observeActiveEraIndex(chainId) } } @@ -76,8 +70,7 @@ class StakingSharedComputation( return computationalCache.useSharedFlow(key, scope) { activeEraFlow(chainId, scope).map { eraIndex -> - val exposures = stakingRepository.getElectedValidatorsExposure(chainId, eraIndex) - exposures to eraIndex + stakingRepository.getElectedValidatorsExposure(chainId, eraIndex) to eraIndex } } } @@ -86,14 +79,14 @@ class StakingSharedComputation( val key = "MIN_STAKE:$chainId" return computationalCache.useSharedFlow(key, scope) { - electedExposuresWithActiveEraFlow(chainId, scope).map { (exposures, activeEraIndex) -> - val minBond = stakingRepository.minimumNominatorBond(chainId) - val bagListLocator = bagListRepository.bagListLocatorOrNull(chainId) - val totalIssuance = totalIssuanceRepository.getTotalIssuance(chainId) - val bagListScoreConverter = BagListScoreConverter.U128(totalIssuance) - val maxElectingVoters = bagListRepository.maxElectingVotes(chainId) - val bagListSize = bagListRepository.bagListSize(chainId) + val minBond = stakingRepository.minimumNominatorBond(chainId) + val bagListLocator = bagListRepository.bagListLocatorOrNull(chainId) + val totalIssuance = totalIssuanceRepository.getTotalIssuance(chainId) + val bagListScoreConverter = BagListScoreConverter.U128(totalIssuance) + val maxElectingVoters = bagListRepository.maxElectingVotes(chainId) + val bagListSize = bagListRepository.bagListSize(chainId) + electedExposuresWithActiveEraFlow(chainId, scope).map { (exposures, activeEraIndex) -> val minStake = minimumStake( exposures = exposures.values, minimumNominatorBond = minBond, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/rewards/RealNominationPoolRewardCalculator.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/rewards/RealNominationPoolRewardCalculator.kt index 3eca41c..4cec59f 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/rewards/RealNominationPoolRewardCalculator.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/common/rewards/RealNominationPoolRewardCalculator.kt @@ -11,12 +11,12 @@ import io.novafoundation.nova.common.utils.reversed import io.novafoundation.nova.feature_account_api.data.model.AccountIdKeyMap import io.novafoundation.nova.feature_account_api.data.model.AccountIdMap import io.novafoundation.nova.feature_staking_api.domain.model.Exposure -import io.novafoundation.nova.feature_staking_api.domain.api.StakingRepository import io.novafoundation.nova.feature_staking_impl.data.StakingOption import io.novafoundation.nova.feature_staking_impl.data.chain import io.novafoundation.nova.feature_staking_api.domain.nominationPool.model.PoolId import io.novafoundation.nova.feature_staking_impl.data.unwrapNominationPools 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.nominationPools.common.NominationPoolSharedComputation import io.novafoundation.nova.feature_staking_impl.domain.rewards.RewardCalculator import kotlinx.coroutines.CoroutineScope @@ -24,29 +24,21 @@ import kotlinx.coroutines.CoroutineScope class NominationPoolRewardCalculatorFactory( private val sharedStakingSharedComputation: StakingSharedComputation, private val nominationPoolSharedComputation: NominationPoolSharedComputation, - private val stakingRepository: StakingRepository, ) { suspend fun create(stakingOption: StakingOption, sharedComputationScope: CoroutineScope): NominationPoolRewardCalculator { - val chain = stakingOption.chain - val chainId = chain.id - // For parachains, staking exposures live on the parent relay chain - val exposureChainId = chain.parentId ?: chainId + val chainId = stakingOption.chain.id val delegateOption = stakingOption.unwrapNominationPools() val delegate = sharedStakingSharedComputation.rewardCalculator(delegateOption, sharedComputationScope) val allPoolAccounts = nominationPoolSharedComputation.allBondedPoolAccounts(chainId, sharedComputationScope) - val poolCommissions = nominationPoolSharedComputation.allBondedPools(chainId, sharedComputationScope) .mapValues { (_, pool) -> pool.commission?.current?.perbill } - val activeEra = stakingRepository.getActiveEraIndex(exposureChainId) - val exposures = stakingRepository.getElectedValidatorsExposure(exposureChainId, activeEra) - return RealNominationPoolRewardCalculator( directStakingDelegate = delegate, - exposures = exposures, + exposures = sharedStakingSharedComputation.electedExposuresInActiveEra(stakingOption.assetWithChain.chain.id, sharedComputationScope), commissions = poolCommissions, poolStashesById = allPoolAccounts ) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/alerts/NominationPoolsAlertsInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/alerts/NominationPoolsAlertsInteractor.kt index bc796e0..7c7e07d 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/alerts/NominationPoolsAlertsInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/alerts/NominationPoolsAlertsInteractor.kt @@ -49,13 +49,11 @@ class RealNominationPoolsAlertsInteractor( return flowOfAll { val poolId = poolMember.poolId val poolStash = poolAccountDerivation.bondedAccountOf(poolId, chain.id) - // Staking exposures live on the relay chain, not on parachains like Asset Hub - val exposureChainId = chain.parentId ?: chain.id combine( nominationPoolsSharedComputation.participatingPoolNominationsFlow(poolStash, poolId, chain.id, shareComputationScope), nominationPoolsSharedComputation.unbondingPoolsFlow(poolId, chain.id, shareComputationScope), - stakingSharedComputation.electedExposuresWithActiveEraFlow(exposureChainId, shareComputationScope), + stakingSharedComputation.electedExposuresWithActiveEraFlow(chain.id, shareComputationScope), ) { poolNominations, unbondingPools, (eraStakers, activeEra) -> val alertsContext = AlertsResolutionContext( eraStakers = eraStakers, diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/stakeSummary/NominationPoolStakeSummaryInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/stakeSummary/NominationPoolStakeSummaryInteractor.kt index e3398b0..e5a3f73 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/stakeSummary/NominationPoolStakeSummaryInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/stakeSummary/NominationPoolStakeSummaryInteractor.kt @@ -47,16 +47,13 @@ class RealNominationPoolStakeSummaryInteractor( stakingOption: StakingOption, sharedComputationScope: CoroutineScope, ): Flow> = flowOfAll { - val chain = stakingOption.assetWithChain.chain - val chainId = chain.id - // Staking exposures live on the relay chain, not on parachains like Asset Hub - val exposureChainId = chain.parentId ?: chainId + val chainId = stakingOption.assetWithChain.chain.id val poolStash = poolAccountDerivation.bondedAccountOf(poolMember.poolId, chainId) combineTransform( nominationPoolSharedComputation.participatingBondedPoolStateFlow(poolStash, poolMember.poolId, chainId, sharedComputationScope), nominationPoolSharedComputation.participatingPoolNominationsFlow(poolStash, poolMember.poolId, chainId, sharedComputationScope), - stakingSharedComputation.electedExposuresWithActiveEraFlow(exposureChainId, sharedComputationScope) + stakingSharedComputation.electedExposuresWithActiveEraFlow(chainId, sharedComputationScope) ) { bondedPoolState, poolNominations, (eraStakers, activeEra) -> val activeStaked = bondedPoolState.amountOf(poolMember.points) diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/userRewards/NomnationPoolsUserRewardsInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/userRewards/NomnationPoolsUserRewardsInteractor.kt index 880f6e6..282e5b0 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/userRewards/NomnationPoolsUserRewardsInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/nominationPools/main/userRewards/NomnationPoolsUserRewardsInteractor.kt @@ -52,9 +52,6 @@ class RealNominationPoolsUserRewardsInteractor( private fun pendingRewardsFlow(accountId: AccountId, chainId: ChainId): Flow { return flowOf { repository.getPendingRewards(accountId, chainId) } - .catch { - Log.e("NominationPoolsUserRewardsInteractor", "Failed to fetch pending rewards", it) - emit(Balance.ZERO) - } + .catch { Log.e("NominationPoolsUserRewardsInteractor", "Failed to fetch pending rewards", it) } } } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/rewards/RewardCalculatorFactory.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/rewards/RewardCalculatorFactory.kt index 0546e62..997761a 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/rewards/RewardCalculatorFactory.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/rewards/RewardCalculatorFactory.kt @@ -13,6 +13,7 @@ import io.novafoundation.nova.feature_staking_impl.data.repository.VaraRepositor import io.novafoundation.nova.feature_staking_impl.data.stakingType import io.novafoundation.nova.feature_staking_impl.data.unwrapNominationPools 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.eraTimeCalculator import io.novafoundation.nova.feature_staking_impl.domain.error.accountIdNotFound import io.novafoundation.nova.runtime.ext.Geneses @@ -46,10 +47,7 @@ class RewardCalculatorFactory( validatorsPrefs: AccountIdMap, scope: CoroutineScope ): RewardCalculator = withContext(Dispatchers.Default) { - // For parachains (e.g. Asset Hub), staking lives on the parent relay chain. - // TotalIssuance must come from there, not from the parachain. - val stakingChainId = stakingOption.assetWithChain.chain.parentId ?: stakingOption.assetWithChain.chain.id - val totalIssuance = totalIssuanceRepository.getTotalIssuance(stakingChainId) + val totalIssuance = totalIssuanceRepository.getTotalIssuance(stakingOption.assetWithChain.chain.id) val validators = exposures.keys.mapNotNull { accountIdHex -> val exposure = exposures[accountIdHex] ?: accountIdNotFound(accountIdHex) @@ -62,20 +60,14 @@ class RewardCalculatorFactory( ) } - stakingOption.createRewardCalculator(validators, totalIssuance, stakingChainId, scope) + stakingOption.createRewardCalculator(validators, totalIssuance, scope) } suspend fun create(stakingOption: StakingOption, scope: CoroutineScope): RewardCalculator = withContext(Dispatchers.Default) { - val chain = stakingOption.assetWithChain.chain - val chainId = chain.id - // For parachains with a parent relay chain, staking exposures live on the relay chain - val exposureChainId = chain.parentId ?: chainId + val chainId = stakingOption.assetWithChain.chain.id - val activeEra = stakingRepository.getActiveEraIndex(exposureChainId) - - val exposures = stakingRepository.getElectedValidatorsExposure(exposureChainId, activeEra) - - val validatorsPrefs = stakingRepository.getValidatorPrefs(exposureChainId, exposures.keys) + val exposures = shareStakingSharedComputation.get().electedExposuresInActiveEra(chainId, scope) + val validatorsPrefs = stakingRepository.getValidatorPrefs(chainId, exposures.keys) create(stakingOption, exposures, validatorsPrefs, scope) } @@ -83,7 +75,6 @@ class RewardCalculatorFactory( private suspend fun StakingOption.createRewardCalculator( validators: List, totalIssuance: BigInteger, - stakingChainId: ChainId, scope: CoroutineScope ): RewardCalculator { return when (unwrapNominationPools().stakingType) { @@ -91,9 +82,8 @@ class RewardCalculatorFactory( val custom = customRelayChainCalculator(validators, totalIssuance, scope) if (custom != null) return custom - // Query parachains from the relay chain, not from Asset Hub - val activePublicParachains = parasRepository.activePublicParachains(stakingChainId) - val inflationConfig = InflationConfig.create(stakingChainId, activePublicParachains) + val activePublicParachains = parasRepository.activePublicParachains(assetWithChain.chain.id) + val inflationConfig = InflationConfig.create(chain.id, activePublicParachains) RewardCurveInflationRewardCalculator(validators, totalIssuance, inflationConfig) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/StartMultiStakingInteractor.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/StartMultiStakingInteractor.kt index 6e52c4a..e0c6e5a 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/StartMultiStakingInteractor.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/common/StartMultiStakingInteractor.kt @@ -26,8 +26,7 @@ class RealStartMultiStakingInteractor( override suspend fun calculateFee(selection: StartMultiStakingSelection): Fee { return withContext(Dispatchers.IO) { - val chain = selection.stakingOption.chain - extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) { + extrinsicService.estimateFee(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) { startStaking(selection) } } @@ -35,8 +34,7 @@ class RealStartMultiStakingInteractor( override suspend fun startStaking(selection: StartMultiStakingSelection): Result { return withContext(Dispatchers.IO) { - val chain = selection.stakingOption.chain - extrinsicService.submitExtrinsicAndAwaitExecution(chain, TransactionOrigin.SelectedWallet) { + extrinsicService.submitExtrinsicAndAwaitExecution(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) { startStaking(selection) }.requireOk() } 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 ae82c35..7ad13a1 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 @@ -98,10 +98,8 @@ private class DirectStakingProperties( enoughAvailableToStake() } - private val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id - override suspend fun minStake(): Balance { - return stakingSharedComputation.minStake(stakingChainId, scope) + return stakingSharedComputation.minStake(stakingOption.chain.id, scope) } private fun StartMultiStakingValidationSystemBuilder.noConflictingStaking() { @@ -127,7 +125,7 @@ private class DirectStakingProperties( private fun StartMultiStakingValidationSystemBuilder.maximumNominatorsReached() { maximumNominatorsReached( stakingRepository = stakingRepository, - chainId = { stakingChainId }, + chainId = { stakingOption.chain.id }, errorProducer = { StartMultiStakingValidationFailure.MaxNominatorsReached(stakingType) } ) } diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt index 56f1ad1..8125696 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/direct/DirectStakingRecommendation.kt @@ -29,8 +29,7 @@ class DirectStakingRecommendation( override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection { val provider = recommendationSettingsProvider.await() - val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id - val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingChainId, stake) + val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingOption.chain.id, stake) val recommendationSettings = provider.recommendedSettings(maximumValidatorsPerNominator) val recommendator = recommendator.await() diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/selectionType/AutomaticMultiStakingSelectionType.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/selectionType/AutomaticMultiStakingSelectionType.kt index d9c0b98..f64b434 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/selectionType/AutomaticMultiStakingSelectionType.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/staking/start/setupAmount/selectionType/AutomaticMultiStakingSelectionType.kt @@ -13,7 +13,6 @@ import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmo import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository import io.novafoundation.nova.feature_wallet_api.domain.model.Asset -import kotlin.coroutines.cancellation.CancellationException class AutomaticMultiStakingSelectionType( private val candidates: List, @@ -58,16 +57,7 @@ class AutomaticMultiStakingSelectionType( } private suspend fun typePropertiesFor(stake: Balance): SingleStakingProperties { - for (candidate in candidates) { - try { - val minStake = candidate.minStake() - if (minStake <= stake) return candidate - } catch (e: CancellationException) { - throw e - } catch (_: Exception) { - } - } - return candidates.findWithMinimumStake() + return candidates.firstAllowingToStake(stake) ?: candidates.findWithMinimumStake() } private suspend fun List.firstAllowingToStake(stake: Balance): SingleStakingProperties? { diff --git a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt index 64f483b..9354cb6 100644 --- a/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt +++ b/feature-staking-impl/src/main/java/io/novafoundation/nova/feature_staking_impl/domain/validators/ValidatorProvider.kt @@ -45,22 +45,20 @@ class ValidatorProvider( ): List { val chain = stakingOption.assetWithChain.chain val chainId = chain.id - // For parachains (e.g. Asset Hub), staking validators live on the parent relay chain - val stakingChainId = chain.parentId ?: chainId val novaValidatorIds = validatorsPreferencesSource.getRecommendedValidatorIds(chainId) - val electedValidatorExposures = stakingSharedComputation.electedExposuresInActiveEra(stakingChainId, scope) + val electedValidatorExposures = stakingSharedComputation.electedExposuresInActiveEra(chainId, scope) val requestedValidatorIds = sources.allValidatorIds(chainId, electedValidatorExposures, novaValidatorIds) // we always need validator prefs for elected validators to construct reward calculator val validatorIdsToQueryPrefs = electedValidatorExposures.keys + requestedValidatorIds - val validatorPrefs = stakingRepository.getValidatorPrefs(stakingChainId, validatorIdsToQueryPrefs) + val validatorPrefs = stakingRepository.getValidatorPrefs(chainId, validatorIdsToQueryPrefs) val identities = identityRepository.getIdentitiesFromIdsHex(chainId, requestedValidatorIds) - val slashes = stakingRepository.getSlashes(stakingChainId, requestedValidatorIds) + val slashes = stakingRepository.getSlashes(chain.id, requestedValidatorIds) val rewardCalculator = rewardCalculatorFactory.create(stakingOption, electedValidatorExposures, validatorPrefs, scope) - val maxNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(stakingChainId) + val maxNominators = stakingConstantsRepository.maxRewardedNominatorPerValidator(chainId) return requestedValidatorIds.map { accountIdHex -> val accountId = AccountIdKey.fromHex(accountIdHex).getOrThrow()