mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-10-20 18:56:25 +00:00
Replace dead legacy-bot status check with a real per-amount reserve check
BridgeViewModel's wUSDT->USDT gate called http://217.77.6.126:3030/status - the legacy single-key bridge bot's endpoint. That service was stopped this same session (see res/validators-tiki.md), so the check always threw, always caught, and always reported "inactive" - the red warning reflected a dead dependency, not real reserve state. Replaced with a direct on-chain read of the multisig's actual USDT balance on Polkadot Asset Hub (BridgeMultisigInteractor.getPolkadotUsdtReserve, via a new Assets.Account storage query mirroring the existing Approvals one). The gate is now per-request: a specific withdrawal is blocked only if it exceeds the real reserve, not on a global on/off flag requiring full 1:1 parity between total circulating supply and total reserve - matching how the Rust listener's own reserve-shortfall check already works. Re-checked on every amount change, not just once per screen load. bridge_wusdt_to_usdt_blocked now takes the available reserve amount as a parameter; updated the English and Turkish strings. Left values-ku untouched deliberately - not fabricating Kurdish translation text without native review.
This commit is contained in:
1 parent
6d27949cbf
commit
a8dc9add24
6 files changed
+69
-35
No files matched your search
@@ -253,7 +253,7 @@ Sizin için en iyi seçeneği bulmak amacıyla tekliflerini karşılaştırın.<
|
|||||||
<string name="bridge_enter_amount">Tutar girin</string>
|
<string name="bridge_enter_amount">Tutar girin</string>
|
||||||
<string name="bridge_hez_to_dot_warning">HEZ → DOT yönü manuel işlenir, 24 saate kadar sürebilir.</string>
|
<string name="bridge_hez_to_dot_warning">HEZ → DOT yönü manuel işlenir, 24 saate kadar sürebilir.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">HEZ → DOT yönü şu anda devre dışı.</string>
|
<string name="bridge_hez_to_dot_blocked">HEZ → DOT yönü şu anda devre dışı.</string>
|
||||||
<string name="bridge_wusdt_to_usdt_blocked">USDT(Pez) → USDT(Pol) yönü şu anda devre dışı.</string>
|
<string name="bridge_wusdt_to_usdt_blocked">Bu tutar köprünün mevcut rezervini (%s USDT) aşıyor. Daha küçük bir tutar deneyin.</string>
|
||||||
<string name="wallet_asset_buy_sell">Al/Sat</string>
|
<string name="wallet_asset_buy_sell">Al/Sat</string>
|
||||||
<string name="account_ledger_import_start_step_otg">Telefon ayarlarınızda %s</string>
|
<string name="account_ledger_import_start_step_otg">Telefon ayarlarınızda %s</string>
|
||||||
<string name="account_ledger_import_start_step_otg_highlighted">OTG\'yi etkinleştir</string>
|
<string name="account_ledger_import_start_step_otg_highlighted">OTG\'yi etkinleştir</string>
|
||||||
|
|||||||
@@ -364,7 +364,7 @@
|
|||||||
<string name="bridge_insufficient_balance">Insufficient balance</string>
|
<string name="bridge_insufficient_balance">Insufficient balance</string>
|
||||||
<string name="bridge_below_minimum">Amount below minimum</string>
|
<string name="bridge_below_minimum">Amount below minimum</string>
|
||||||
<string name="bridge_enter_amount">Enter amount</string>
|
<string name="bridge_enter_amount">Enter amount</string>
|
||||||
<string name="bridge_wusdt_to_usdt_blocked">USDT(Pez)→USDT(Pol) swaps are temporarily unavailable. Waiting for 1:1 liquidity to be established.</string>
|
<string name="bridge_wusdt_to_usdt_blocked">This amount exceeds the bridge\'s current available reserve (%s USDT). Try a smaller amount.</string>
|
||||||
|
|
||||||
<string name="bridge_sign_status_ok">Bridge allowance OK (%s)</string>
|
<string name="bridge_sign_status_ok">Bridge allowance OK (%s)</string>
|
||||||
<string name="bridge_sign_button">Sign renewal (%s)</string>
|
<string name="bridge_sign_button">Sign renewal (%s)</string>
|
||||||
|
|||||||
+7
@@ -14,8 +14,15 @@ package io.novafoundation.nova.feature_assets.domain.bridge.multisig
|
|||||||
object BridgeMultisigConstants {
|
object BridgeMultisigConstants {
|
||||||
|
|
||||||
const val WUSDT_ASSET_ID = 1000
|
const val WUSDT_ASSET_ID = 1000
|
||||||
|
const val POLKADOT_USDT_ASSET_ID = 1
|
||||||
|
|
||||||
const val MULTISIG_ADDRESS = "5GvwxmCDp3PC33KHoeWSgj3S7ocE7nzk1jiCCZMPSDBFeNcj"
|
const val MULTISIG_ADDRESS = "5GvwxmCDp3PC33KHoeWSgj3S7ocE7nzk1jiCCZMPSDBFeNcj"
|
||||||
|
/** Same multisig account, Polkadot Asset Hub SS58 encoding - where the real USDT backing
|
||||||
|
* wUSDT withdrawals actually sits. Used to check a specific withdrawal amount against the
|
||||||
|
* real reserve (see BridgeMultisigInteractor.getPolkadotUsdtReserve) instead of the old
|
||||||
|
* dead-external-service boolean check this replaced (217.77.6.126:3030/status belonged to
|
||||||
|
* the legacy bridge bot, stopped this same session - see res/validators-tiki.md). */
|
||||||
|
const val MULTISIG_ADDRESS_POLKADOT = "15sF76THfpefUaKomHZSpssayRbsp6Yt6ESgMrLjzJCmpe66"
|
||||||
const val AUTOMATION_KEY_ADDRESS = "5GQu4PFUb1f3MTJ7i7c1CtLgDk3TVvpSW1VbQCRmfkMoC8cM"
|
const val AUTOMATION_KEY_ADDRESS = "5GQu4PFUb1f3MTJ7i7c1CtLgDk3TVvpSW1VbQCRmfkMoC8cM"
|
||||||
|
|
||||||
const val THRESHOLD = 3
|
const val THRESHOLD = 3
|
||||||
|
|||||||
+20
@@ -38,6 +38,14 @@ interface BridgeMultisigInteractor {
|
|||||||
suspend fun getSignerState(): BridgeSignerState?
|
suspend fun getSignerState(): BridgeSignerState?
|
||||||
|
|
||||||
suspend fun submitRenewalSignature(): Result<ExtrinsicExecutionResult>
|
suspend fun submitRenewalSignature(): Result<ExtrinsicExecutionResult>
|
||||||
|
|
||||||
|
/** Real USDT (base units) the multisig actually holds on Polkadot Asset Hub right now - the
|
||||||
|
* true backing for wUSDT->USDT withdrawals. Replaces the old wusdtToUsdtActive boolean
|
||||||
|
* fetched from the legacy bridge bot's :3030/status endpoint, which this session stopped
|
||||||
|
* (see res/validators-tiki.md) - that made the old check always report "inactive"
|
||||||
|
* regardless of real reserve. A specific withdrawal should be allowed whenever it's covered
|
||||||
|
* by this real balance, not gated on an unrelated dead service or on total supply parity. */
|
||||||
|
suspend fun getPolkadotUsdtReserve(): BigInteger
|
||||||
}
|
}
|
||||||
|
|
||||||
@FeatureScope
|
@FeatureScope
|
||||||
@@ -118,6 +126,18 @@ class RealBridgeMultisigInteractor @Inject constructor(
|
|||||||
}.getOrThrow().requireOk()
|
}.getOrThrow().requireOk()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPolkadotUsdtReserve(): BigInteger {
|
||||||
|
val polkadotChain = chainRegistry.getChain(ChainGeneses.POLKADOT_ASSET_HUB)
|
||||||
|
val multisigAccountId = BridgeMultisigConstants.MULTISIG_ADDRESS_POLKADOT.toAccountId().intoKey()
|
||||||
|
|
||||||
|
return storageDataSource.query(polkadotChain.id) {
|
||||||
|
runtime.metadata.bridgeAssets().assetBalance.query(
|
||||||
|
BridgeMultisigConstants.POLKADOT_USDT_ASSET_ID.toBigInteger(),
|
||||||
|
multisigAccountId,
|
||||||
|
)
|
||||||
|
} ?: BigInteger.ZERO
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun queryRemainingAllowance(chain: Chain): BigInteger {
|
private suspend fun queryRemainingAllowance(chain: Chain): BigInteger {
|
||||||
val multisigAccountId = BridgeMultisigConstants.MULTISIG_ADDRESS.toAccountId().intoKey()
|
val multisigAccountId = BridgeMultisigConstants.MULTISIG_ADDRESS.toAccountId().intoKey()
|
||||||
val delegateAccountId = BridgeMultisigConstants.AUTOMATION_KEY_ADDRESS.toAccountId().intoKey()
|
val delegateAccountId = BridgeMultisigConstants.AUTOMATION_KEY_ADDRESS.toAccountId().intoKey()
|
||||||
|
|||||||
+12
@@ -48,6 +48,18 @@ val BridgeAssetsApi.approvalAmount: QueryableStorageEntry3<BigInteger, AccountId
|
|||||||
key3FromInternalConverter = AccountIdKey.scaleDecoder,
|
key3FromInternalConverter = AccountIdKey.scaleDecoder,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** `Assets.Account(asset_id, account) -> AssetAccount { balance, ... }` - only `balance` needed.
|
||||||
|
* Used to check a custody account's real held amount (e.g. the multisig's real USDT reserve on
|
||||||
|
* Polkadot Asset Hub), not the connected wallet's own balance. */
|
||||||
|
context(StorageQueryContext)
|
||||||
|
val BridgeAssetsApi.assetBalance: QueryableStorageEntry2<BigInteger, AccountIdKey, BigInteger>
|
||||||
|
get() = storage2(
|
||||||
|
name = "Account",
|
||||||
|
binding = { decoded, _, _ -> bindNumber(decoded.castToStruct()["balance"]) },
|
||||||
|
key2ToInternalConverter = AccountIdKey.scaleEncoder,
|
||||||
|
key2FromInternalConverter = AccountIdKey.scaleDecoder,
|
||||||
|
)
|
||||||
|
|
||||||
@JvmInline
|
@JvmInline
|
||||||
value class BridgeMultisigApi(override val module: Module) : QueryableModule
|
value class BridgeMultisigApi(override val module: Module) : QueryableModule
|
||||||
|
|
||||||
|
|||||||
+28
-33
@@ -21,14 +21,10 @@ import io.novafoundation.nova.runtime.ext.addressOf
|
|||||||
import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard
|
import io.novafoundation.nova.runtime.ext.displayNameWithAssetStandard
|
||||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||||
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
|
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.json.JSONObject
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
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
|
* DOT<->HEZ used to be a second pair here, retired 2026-07 in favor of the multisig-custodied
|
||||||
@@ -60,7 +56,8 @@ class BridgeViewModel(
|
|||||||
const val FEE_PERCENT = 0.001
|
const val FEE_PERCENT = 0.001
|
||||||
const val MIN_USDT = 1.0
|
const val MIN_USDT = 1.0
|
||||||
|
|
||||||
const val BRIDGE_STATUS_API = "http://217.77.6.126:3030/status"
|
// USDT has 6 decimals on both Polkadot and Pezkuwi Asset Hub.
|
||||||
|
val USDT_DECIMALS_DIVISOR: BigDecimal = BigDecimal.TEN.pow(6)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _pair = MutableLiveData(BridgePair.USDT)
|
private val _pair = MutableLiveData(BridgePair.USDT)
|
||||||
@@ -121,12 +118,15 @@ class BridgeViewModel(
|
|||||||
val fillAmountEvent: LiveData<Event<String>> = _fillAmountEvent
|
val fillAmountEvent: LiveData<Event<String>> = _fillAmountEvent
|
||||||
|
|
||||||
private var currentAmount: Double = 0.0
|
private var currentAmount: Double = 0.0
|
||||||
private var isWusdtToUsdtActive: Boolean = false
|
/** Real USDT the multisig actually holds on Polkadot Asset Hub - see
|
||||||
|
* BridgeMultisigInteractor.getPolkadotUsdtReserve for why this replaced a dead external
|
||||||
|
* status check that always reported "inactive" regardless of the real reserve. */
|
||||||
|
private var polkadotUsdtReserve: BigDecimal = BigDecimal.ZERO
|
||||||
private var availableBalance: BigDecimal = BigDecimal.ZERO
|
private var availableBalance: BigDecimal = BigDecimal.ZERO
|
||||||
private var balanceJob: Job? = null
|
private var balanceJob: Job? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
fetchBridgeStatus()
|
fetchReserveStatus()
|
||||||
refreshSignerState()
|
refreshSignerState()
|
||||||
updateCards()
|
updateCards()
|
||||||
loadPairOptions()
|
loadPairOptions()
|
||||||
@@ -171,7 +171,7 @@ class BridgeViewModel(
|
|||||||
currentAmount = amount
|
currentAmount = amount
|
||||||
calculateOutput()
|
calculateOutput()
|
||||||
updateInsufficientBalanceState()
|
updateInsufficientBalanceState()
|
||||||
updateButtonState()
|
updateWarningState() // re-check the entered amount against the cached reserve
|
||||||
}
|
}
|
||||||
|
|
||||||
fun maxClicked() {
|
fun maxClicked() {
|
||||||
@@ -208,27 +208,15 @@ class BridgeViewModel(
|
|||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchBridgeStatus() {
|
private fun fetchReserveStatus() {
|
||||||
launch {
|
launch {
|
||||||
try {
|
polkadotUsdtReserve = try {
|
||||||
isWusdtToUsdtActive = withContext(Dispatchers.IO) {
|
val raw = bridgeMultisigInteractor.getPolkadotUsdtReserve()
|
||||||
fetchStatusFromApi()
|
BigDecimal(raw).divide(USDT_DECIMALS_DIVISOR)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
BigDecimal.ZERO
|
||||||
}
|
}
|
||||||
updateWarningState()
|
updateWarningState()
|
||||||
} catch (e: Exception) {
|
|
||||||
isWusdtToUsdtActive = false
|
|
||||||
updateWarningState()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fetchStatusFromApi(): Boolean {
|
|
||||||
return try {
|
|
||||||
val response = URL(BRIDGE_STATUS_API).readText()
|
|
||||||
val json = JSONObject(response)
|
|
||||||
json.optBoolean("wusdtToUsdtActive", false)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,14 +225,20 @@ class BridgeViewModel(
|
|||||||
|
|
||||||
when (dir) {
|
when (dir) {
|
||||||
BridgeDirection.WUSDT_TO_USDT -> {
|
BridgeDirection.WUSDT_TO_USDT -> {
|
||||||
_showWarning.postValue(true)
|
val requested = BigDecimal.valueOf(currentAmount)
|
||||||
if (!isWusdtToUsdtActive) {
|
val exceedsReserve = currentAmount > 0 && requested > polkadotUsdtReserve
|
||||||
|
_showWarning.postValue(exceedsReserve)
|
||||||
|
if (exceedsReserve) {
|
||||||
_warningBlocked.postValue(true)
|
_warningBlocked.postValue(true)
|
||||||
_warningText.postValue(resourceManager.getString(R.string.bridge_wusdt_to_usdt_blocked))
|
_warningText.postValue(
|
||||||
|
resourceManager.getString(
|
||||||
|
R.string.bridge_wusdt_to_usdt_blocked,
|
||||||
|
polkadotUsdtReserve.setScale(2, RoundingMode.DOWN).stripTrailingZeros().toPlainString()
|
||||||
|
)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
_warningBlocked.postValue(false)
|
_warningBlocked.postValue(false)
|
||||||
_warningText.postValue("")
|
_warningText.postValue("")
|
||||||
_showWarning.postValue(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
@@ -274,11 +268,12 @@ class BridgeViewModel(
|
|||||||
private fun updateButtonState() {
|
private fun updateButtonState() {
|
||||||
val dir = _direction.value ?: return
|
val dir = _direction.value ?: return
|
||||||
|
|
||||||
|
val requested = BigDecimal.valueOf(currentAmount)
|
||||||
_buttonState.value = when {
|
_buttonState.value = when {
|
||||||
currentAmount <= 0 -> ButtonState.DISABLED
|
currentAmount <= 0 -> ButtonState.DISABLED
|
||||||
currentAmount < MIN_USDT -> ButtonState.DISABLED
|
currentAmount < MIN_USDT -> ButtonState.DISABLED
|
||||||
BigDecimal.valueOf(currentAmount) > availableBalance -> ButtonState.DISABLED
|
requested > availableBalance -> ButtonState.DISABLED
|
||||||
dir == BridgeDirection.WUSDT_TO_USDT && !isWusdtToUsdtActive -> ButtonState.DISABLED
|
dir == BridgeDirection.WUSDT_TO_USDT && requested > polkadotUsdtReserve -> ButtonState.DISABLED
|
||||||
else -> ButtonState.NORMAL
|
else -> ButtonState.NORMAL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +287,7 @@ class BridgeViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun refreshBridgeStatus() {
|
fun refreshBridgeStatus() {
|
||||||
fetchBridgeStatus()
|
fetchReserveStatus()
|
||||||
refreshSignerState()
|
refreshSignerState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user