From 7a2d330a0a1880fcfd42bf0bc214346f82851ca4 Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Tue, 14 Jul 2026 14:41:56 -0700 Subject: [PATCH] Retire DOT-HEZ bridge from the Bridge screen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DOT<->HEZ bridge backend (the legacy single-key bot) is being permanently decommissioned - it had zero multisig protection and no liquidity gate at all on that leg (unconditionally "active" regardless of pool state, the single least-safe path in the whole bridge). Once USDT bridging is solid, HEZ is reachable indirectly (bridge USDT, swap for HEZ within Pezkuwi's own ecosystem) without a dedicated native bridge that would need its own from-scratch security work (native currency has no Assets.approve_transfer-style delegation to reuse). - BridgePair/BridgeDirection collapsed to USDT-only (USDT_TO_WUSDT/ WUSDT_TO_USDT) - kept the pair/pairOptions/picker structure itself since it costs nothing and is the natural seam for a future pair, rather than hardcoding a single value inline. - Removed the now-dead DOT/HEZ exchange-rate fetching (CoinGecko polling, fallback rate) and liquidity-gate logic entirely, not just the unreachable enum branches. - Removed the separate "Bridge DOT ↔ HEZ" entry from the asset selector bottom sheet (BuySellSelectorMixin) - both it and "Bridge USDT" opened the exact same screen with no preset, so keeping a DOT-HEZ-labeled entry pointing at a USDT-only screen would have been actively misleading, not just redundant. - bridge_title was the generic toolbar/picker-sheet title but hardcoded "DOT ↔ HEZ Bridge" regardless of pair - fixed to "USDT Bridge". - Removed now-unreferenced strings (wallet_asset_bridge, bridge_pair_dot_hez, bridge_rate_format(_reverse), bridge_hez_to_dot_note/warning/blocked) from the base strings.xml. Confirmed via research: feature-bridge-api/feature-bridge-impl modules exist but are unrelated orphaned scaffolding, not depended on by :app, zero code overlap with this screen - correctly out of scope here. --- common/src/main/res/values/strings.xml | 9 +- .../common/buySell/BuySellSelectorMixin.kt | 11 -- .../presentation/bridge/BridgeFragment.kt | 7 +- .../presentation/bridge/BridgeViewModel.kt | 148 +++--------------- .../src/main/res/layout/fragment_bridge.xml | 6 +- 5 files changed, 32 insertions(+), 149 deletions(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index f7a6c6c2..6fff5081 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -350,27 +350,20 @@ Sell tokens Buy tokens - Bridge DOT ↔ HEZ Bridge USDT(DOT) ↔ USDT(HEZ) - DOT ↔ HEZ Bridge - HEZ ⇄ DOT + USDT Bridge USDT ⇄ USDT.p You send You receive (estimated) Exchange rate Bridge fee Minimum - 1 DOT = %s HEZ - 1 HEZ = %s DOT Swap - HEZ→DOT swaps are processed when sufficient DOT liquidity is available. Insufficient balance Amount below minimum 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. Bridge allowance OK (%s) 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 3438c152..50ed8265 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,6 @@ class RealBuySellSelectorMixin( private suspend fun openAllAssetsSelector() = BuySellSelectorMixin.SelectorPayload( buyItem(enabled = true) { router.openBuyFlow() }, sellItem(enabled = buySellRestrictionCheckMixin.isAllowed()) { router.openSellFlow() }, - bridgeItem(enabled = true) { router.openBridgeFlow() }, bridgeUsdtItem(enabled = true) { router.openBridgeFlow() } ) @@ -116,16 +115,6 @@ class RealBuySellSelectorMixin( ) } - private fun bridgeItem(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, - if (enabled) R.color.text_primary else R.color.button_text_inactive, - action - ) - } - private fun bridgeUsdtItem(enabled: Boolean, action: () -> Unit): ListSelectorMixin.Item { return ListSelectorMixin.Item( R.drawable.ic_bridge, 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 6f53ab62..2c89a028 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 @@ -36,8 +36,8 @@ class BridgeFragment : BaseFragment() { // One-tap direction flip - replaces the old segmented direction buttons binder.bridgeFlipButton.setOnClickListener { when (viewModel.direction.value) { - BridgeDirection.DOT_TO_HEZ, BridgeDirection.USDT_TO_WUSDT -> viewModel.setDirectionRight() - BridgeDirection.HEZ_TO_DOT, BridgeDirection.WUSDT_TO_USDT -> viewModel.setDirectionLeft() + BridgeDirection.USDT_TO_WUSDT -> viewModel.setDirectionRight() + BridgeDirection.WUSDT_TO_USDT -> viewModel.setDirectionLeft() null -> Unit } } @@ -160,13 +160,10 @@ class BridgeFragment : BaseFragment() { } enum class BridgePair { - DOT_HEZ, USDT } enum class BridgeDirection { - DOT_TO_HEZ, - 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 8c506231..f15b02d6 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 @@ -30,6 +30,14 @@ import java.math.BigDecimal import java.math.RoundingMode import java.net.URL +/** + * DOT<->HEZ used to be a second pair here, retired 2026-07 in favor of the multisig-custodied + * USDT<->wUSDT pair only - the legacy single-key bot that executed it (and every other swap on + * this screen) was replaced by the detect-only Rust listener + pwap-web multisig approval flow + * (see /home/myhez/res/validators-tiki.md). Kept the BridgePair/pairOptions/picker structure + * (rather than collapsing to a single hardcoded pair) since it costs nothing and is exactly the + * seam a future new pair would reuse. + */ class BridgeViewModel( private val router: AssetsRouter, private val resourceManager: ResourceManager, @@ -45,26 +53,20 @@ class BridgeViewModel( val POLKADOT_ASSET_HUB_ID = ChainGeneses.POLKADOT_ASSET_HUB val PEZKUWI_ASSET_HUB_ID = ChainGeneses.PEZKUWI_ASSET_HUB - const val UTILITY_ASSET_ID = 0 - // 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 const val FEE_PERCENT = 0.001 - const val MIN_DOT = 0.1 - const val MIN_HEZ = 0.3 const val MIN_USDT = 1.0 - const val COINGECKO_API = "https://api.coingecko.com/api/v3/simple/price?ids=polkadot,hezkurd&vs_currencies=usd" const val BRIDGE_STATUS_API = "http://217.77.6.126:3030/status" } - private val _pair = MutableLiveData(BridgePair.DOT_HEZ) + private val _pair = MutableLiveData(BridgePair.USDT) val pair: LiveData = _pair - private val _direction = MutableLiveData(BridgeDirection.DOT_TO_HEZ) + private val _direction = MutableLiveData(BridgeDirection.USDT_TO_WUSDT) val direction: LiveData = _direction private val _outputAmount = MutableLiveData("0.0") @@ -119,28 +121,23 @@ class BridgeViewModel( val fillAmountEvent: LiveData> = _fillAmountEvent private var currentAmount: Double = 0.0 - private var dotToHezRate: Double = FALLBACK_RATE - private var isHezToDotActive: Boolean = false private var isWusdtToUsdtActive: Boolean = false private var availableBalance: BigDecimal = BigDecimal.ZERO private var balanceJob: Job? = null init { - fetchExchangeRate() fetchBridgeStatus() refreshSignerState() updateCards() loadPairOptions() + 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 - } + _direction.value = BridgeDirection.USDT_TO_WUSDT updateUI() calculateOutput() updateWarningState() @@ -149,11 +146,7 @@ class BridgeViewModel( } 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 - } + val newDir = BridgeDirection.USDT_TO_WUSDT if (_direction.value != newDir) { _direction.value = newDir updateUI() @@ -164,11 +157,7 @@ class BridgeViewModel( } 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 - } + val newDir = BridgeDirection.WUSDT_TO_USDT if (_direction.value != newDir) { _direction.value = newDir updateUI() @@ -195,15 +184,11 @@ class BridgeViewModel( launch { val chainId = when (dir) { - 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 } 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 } @@ -223,63 +208,27 @@ 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) { + isWusdtToUsdtActive = withContext(Dispatchers.IO) { fetchStatusFromApi() } - isHezToDotActive = hezToDot - isWusdtToUsdtActive = wusdtToUsdt updateWarningState() } catch (e: Exception) { - isHezToDotActive = false isWusdtToUsdtActive = false updateWarningState() } } } - private fun fetchStatusFromApi(): Pair { + private fun fetchStatusFromApi(): Boolean { 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) + json.optBoolean("wusdtToUsdtActive", false) } catch (e: Exception) { - Pair(false, false) + false } } @@ -287,16 +236,6 @@ class BridgeViewModel( 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) { @@ -316,16 +255,7 @@ class BridgeViewModel( } 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 - } - - val netOutput = grossOutput * (1 - FEE_PERCENT) + val netOutput = currentAmount * (1 - FEE_PERCENT) // 1:1, fee-adjusted _outputAmount.value = if (netOutput > 0) { BigDecimal(netOutput).setScale(6, RoundingMode.DOWN).stripTrailingZeros().toPlainString() @@ -335,41 +265,19 @@ class BridgeViewModel( } private fun updateUI() { - val dir = _direction.value ?: return - - 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 -> { - 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" - } - } + _exchangeRateText.value = "1:1 (fee 0.1%)" + _minimumText.value = "$MIN_USDT USDT" updateButtonState() } private fun updateButtonState() { val dir = _direction.value ?: return - 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 + currentAmount < MIN_USDT -> ButtonState.DISABLED BigDecimal.valueOf(currentAmount) > availableBalance -> ButtonState.DISABLED - dir == BridgeDirection.HEZ_TO_DOT && !isHezToDotActive -> ButtonState.DISABLED dir == BridgeDirection.WUSDT_TO_USDT && !isWusdtToUsdtActive -> ButtonState.DISABLED else -> ButtonState.NORMAL } @@ -443,20 +351,18 @@ class BridgeViewModel( // Same chainId/assetId mapping already used by swapClicked() to resolve the origin side - // mirrored here (plus its destination counterpart) purely to display logos/names, no new business rule. val originChainId = when (dir) { - BridgeDirection.DOT_TO_HEZ, BridgeDirection.USDT_TO_WUSDT -> POLKADOT_ASSET_HUB_ID - BridgeDirection.HEZ_TO_DOT, BridgeDirection.WUSDT_TO_USDT -> PEZKUWI_ASSET_HUB_ID + BridgeDirection.USDT_TO_WUSDT -> POLKADOT_ASSET_HUB_ID + BridgeDirection.WUSDT_TO_USDT -> PEZKUWI_ASSET_HUB_ID } val destChainId = when (dir) { - BridgeDirection.DOT_TO_HEZ, BridgeDirection.USDT_TO_WUSDT -> PEZKUWI_ASSET_HUB_ID - BridgeDirection.HEZ_TO_DOT, BridgeDirection.WUSDT_TO_USDT -> POLKADOT_ASSET_HUB_ID + BridgeDirection.USDT_TO_WUSDT -> PEZKUWI_ASSET_HUB_ID + BridgeDirection.WUSDT_TO_USDT -> POLKADOT_ASSET_HUB_ID } val originAssetId = when (dir) { - BridgeDirection.DOT_TO_HEZ, BridgeDirection.HEZ_TO_DOT -> UTILITY_ASSET_ID BridgeDirection.USDT_TO_WUSDT -> POLKADOT_USDT_ASSET_ID BridgeDirection.WUSDT_TO_USDT -> PEZKUWI_USDT_ASSET_ID } val destAssetId = when (dir) { - BridgeDirection.DOT_TO_HEZ, BridgeDirection.HEZ_TO_DOT -> UTILITY_ASSET_ID BridgeDirection.USDT_TO_WUSDT -> PEZKUWI_USDT_ASSET_ID BridgeDirection.WUSDT_TO_USDT -> POLKADOT_USDT_ASSET_ID } @@ -485,11 +391,9 @@ class BridgeViewModel( private fun loadPairOptions() { launch { - val dotHezIcon = cardUiFor(POLKADOT_ASSET_HUB_ID, UTILITY_ASSET_ID).assetIcon val usdtIcon = cardUiFor(POLKADOT_ASSET_HUB_ID, POLKADOT_USDT_ASSET_ID).assetIcon _pairOptions.value = listOf( - BridgePairUi(BridgePair.DOT_HEZ, dotHezIcon, resourceManager.getString(R.string.bridge_pair_dot_hez)), BridgePairUi(BridgePair.USDT, usdtIcon, resourceManager.getString(R.string.bridge_pair_usdt)) ) } diff --git a/feature-assets/src/main/res/layout/fragment_bridge.xml b/feature-assets/src/main/res/layout/fragment_bridge.xml index fb953eac..59feac79 100644 --- a/feature-assets/src/main/res/layout/fragment_bridge.xml +++ b/feature-assets/src/main/res/layout/fragment_bridge.xml @@ -113,7 +113,7 @@ android:layout_height="wrap_content" android:textColor="@color/text_primary" android:textSize="14sp" - tools:text="1 DOT = 3.00 HEZ" /> + tools:text="1:1 (fee 0.1%)" /> @@ -162,7 +162,7 @@ android:layout_height="wrap_content" android:textColor="@color/text_primary" android:textSize="14sp" - tools:text="0.1 DOT" /> + tools:text="1.0 USDT" /> @@ -170,7 +170,7 @@ - +