mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 17:15:48 +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:
+7
@@ -14,8 +14,15 @@ package io.novafoundation.nova.feature_assets.domain.bridge.multisig
|
||||
object BridgeMultisigConstants {
|
||||
|
||||
const val WUSDT_ASSET_ID = 1000
|
||||
const val POLKADOT_USDT_ASSET_ID = 1
|
||||
|
||||
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 THRESHOLD = 3
|
||||
|
||||
+20
@@ -38,6 +38,14 @@ interface BridgeMultisigInteractor {
|
||||
suspend fun getSignerState(): BridgeSignerState?
|
||||
|
||||
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
|
||||
@@ -118,6 +126,18 @@ class RealBridgeMultisigInteractor @Inject constructor(
|
||||
}.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 {
|
||||
val multisigAccountId = BridgeMultisigConstants.MULTISIG_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,
|
||||
)
|
||||
|
||||
/** `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
|
||||
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.multiNetwork.ChainRegistry
|
||||
import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONObject
|
||||
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
|
||||
@@ -60,7 +56,8 @@ class BridgeViewModel(
|
||||
const val FEE_PERCENT = 0.001
|
||||
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)
|
||||
@@ -121,12 +118,15 @@ class BridgeViewModel(
|
||||
val fillAmountEvent: LiveData<Event<String>> = _fillAmountEvent
|
||||
|
||||
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 balanceJob: Job? = null
|
||||
|
||||
init {
|
||||
fetchBridgeStatus()
|
||||
fetchReserveStatus()
|
||||
refreshSignerState()
|
||||
updateCards()
|
||||
loadPairOptions()
|
||||
@@ -171,7 +171,7 @@ class BridgeViewModel(
|
||||
currentAmount = amount
|
||||
calculateOutput()
|
||||
updateInsufficientBalanceState()
|
||||
updateButtonState()
|
||||
updateWarningState() // re-check the entered amount against the cached reserve
|
||||
}
|
||||
|
||||
fun maxClicked() {
|
||||
@@ -208,27 +208,15 @@ class BridgeViewModel(
|
||||
router.back()
|
||||
}
|
||||
|
||||
private fun fetchBridgeStatus() {
|
||||
private fun fetchReserveStatus() {
|
||||
launch {
|
||||
try {
|
||||
isWusdtToUsdtActive = withContext(Dispatchers.IO) {
|
||||
fetchStatusFromApi()
|
||||
}
|
||||
updateWarningState()
|
||||
polkadotUsdtReserve = try {
|
||||
val raw = bridgeMultisigInteractor.getPolkadotUsdtReserve()
|
||||
BigDecimal(raw).divide(USDT_DECIMALS_DIVISOR)
|
||||
} catch (e: Exception) {
|
||||
isWusdtToUsdtActive = false
|
||||
updateWarningState()
|
||||
BigDecimal.ZERO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
updateWarningState()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,14 +225,20 @@ class BridgeViewModel(
|
||||
|
||||
when (dir) {
|
||||
BridgeDirection.WUSDT_TO_USDT -> {
|
||||
_showWarning.postValue(true)
|
||||
if (!isWusdtToUsdtActive) {
|
||||
val requested = BigDecimal.valueOf(currentAmount)
|
||||
val exceedsReserve = currentAmount > 0 && requested > polkadotUsdtReserve
|
||||
_showWarning.postValue(exceedsReserve)
|
||||
if (exceedsReserve) {
|
||||
_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 {
|
||||
_warningBlocked.postValue(false)
|
||||
_warningText.postValue("")
|
||||
_showWarning.postValue(false)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@@ -274,11 +268,12 @@ class BridgeViewModel(
|
||||
private fun updateButtonState() {
|
||||
val dir = _direction.value ?: return
|
||||
|
||||
val requested = BigDecimal.valueOf(currentAmount)
|
||||
_buttonState.value = when {
|
||||
currentAmount <= 0 -> ButtonState.DISABLED
|
||||
currentAmount < MIN_USDT -> ButtonState.DISABLED
|
||||
BigDecimal.valueOf(currentAmount) > availableBalance -> ButtonState.DISABLED
|
||||
dir == BridgeDirection.WUSDT_TO_USDT && !isWusdtToUsdtActive -> ButtonState.DISABLED
|
||||
requested > availableBalance -> ButtonState.DISABLED
|
||||
dir == BridgeDirection.WUSDT_TO_USDT && requested > polkadotUsdtReserve -> ButtonState.DISABLED
|
||||
else -> ButtonState.NORMAL
|
||||
}
|
||||
}
|
||||
@@ -292,7 +287,7 @@ class BridgeViewModel(
|
||||
}
|
||||
|
||||
fun refreshBridgeStatus() {
|
||||
fetchBridgeStatus()
|
||||
fetchReserveStatus()
|
||||
refreshSignerState()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user