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 @@
-
+