Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,11 @@ class Web3ViewModel @Inject constructor(
}
}

suspend fun estimateBtcFeeRate(rawTransactionHex: String, currentRate: String?): EstimateFeeResponse? {
val cleanedRawHex: String = rawTransactionHex.removePrefix("0x").trim()
suspend fun estimateBtcFeeRate(rawTransactionHex: String? = null, currentRate: String?): EstimateFeeResponse? {
val cleanedRawHex: String? = rawTransactionHex
?.removePrefix("0x")
?.trim()
?.takeIf { it.isNotEmpty() }
val response = withContext(Dispatchers.IO) {
runCatching {
web3Repository.estimateFee(
Expand Down Expand Up @@ -536,4 +539,4 @@ class Web3ViewModel @Inject constructor(
return@withContext null
}
}
}
}
39 changes: 36 additions & 3 deletions app/src/main/java/one/mixin/android/ui/wallet/InputFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1446,19 +1446,40 @@ class InputFragment : BaseFragment(R.layout.fragment_input), OnReceiveSelectionC
null
}
if (transaction == null) {
delay(3000)
refreshGas(t)
val handledByBtcFallback = t.chainId == Constants.ChainId.BITCOIN_CHAIN_ID &&
isAdded &&
applyFallbackBtcFeeWithoutRawTransaction(t)
if (handledByBtcFallback) {
binding.iconImageView.isVisible = false
binding.contentTextView.isVisible = true
binding.loadingProgressBar.isVisible = false
applyFeeUi()
return
Comment on lines +1452 to +1457
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback handling repeats the same UI state toggles (iconImageView/contentTextView/loadingProgressBar + applyFeeUi()) in multiple branches. This duplication makes it easy for the branches to drift (e.g., one path missing a UI update). Consider extracting a small helper to apply the common UI state for "fee resolved" and call it from all branches.

Copilot uses AI. Check for mistakes.
}
delay(3000)
refreshGas(t)
return
Comment on lines +1459 to +1461
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refreshGas retries by calling itself after delay(3000) (both when transaction == null and when gas == null). If the underlying condition never resolves (e.g., persistent BTC UTXO/build failures), this creates unbounded recursion and can eventually overflow the stack. Prefer an iterative retry loop (or a bounded retry with backoff) and ensure it stops when the coroutine is cancelled / fragment is no longer active.

Copilot uses AI. Check for mistakes.
} else if (isAdded) {
val estimate= web3ViewModel.calcFee(t, transaction, fromAddress)
gas = estimate.fee
rate = estimate.rate
miniFee = estimate.minFee
if (gas == null) {
val handledByBtcFallback = t.chainId == Constants.ChainId.BITCOIN_CHAIN_ID &&
applyFallbackBtcFeeWithoutRawTransaction(t)
if (handledByBtcFallback) {
binding.iconImageView.isVisible = false
binding.contentTextView.isVisible = true
binding.loadingProgressBar.isVisible = false
applyFeeUi()
return
}
delay(3000)
if (dialog.isShowing) {
dialog.dismiss()
}
refreshGas(t)
return
}
if (chainToken?.assetId == t.assetId) {
val balance = runCatching {
Expand Down Expand Up @@ -1509,11 +1530,23 @@ class InputFragment : BaseFragment(R.layout.fragment_input), OnReceiveSelectionC
applyFeeUi()
}

private suspend fun applyFallbackBtcFeeWithoutRawTransaction(t: Web3TokenItem): Boolean {
val estimate = web3ViewModel.estimateBtcFeeRate(currentRate = rate?.toPlainString()) ?: return false
rate = estimate.feeRate?.toBigDecimalOrNull() ?: rate
miniFee = estimate.minFee ?: miniFee
val fallbackFee: BigDecimal = estimate.minFee?.toBigDecimalOrNull()?.movePointLeft(1) ?: return false
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fallbackFee is derived from estimate.minFee by doing movePointLeft(1), which changes the unit/scale (÷10) compared to how minFee is treated elsewhere (e.g., Web3ViewModel.calcFee compares minFee directly against a BTC-denominated estimatedFee). This is likely to under-estimate the BTC fee and can produce invalid/underfunded transactions. Consider using estimate.minFee directly (same unit as calcFee) or clearly converting based on documented API units, but keep it consistent across the codepaths.

Suggested change
val fallbackFee: BigDecimal = estimate.minFee?.toBigDecimalOrNull()?.movePointLeft(1) ?: return false
val fallbackFee: BigDecimal = estimate.minFee?.toBigDecimalOrNull() ?: return false

Copilot uses AI. Check for mistakes.
gas = fallbackFee
binding.insufficientFeeBalance.text = getString(R.string.insufficient_gas, chainToken?.symbol)
binding.contentTextView.text = "${fallbackFee.numberFormat8()} ${chainToken?.symbol ?: t.getChainSymbolFromName()}"
updateAvailableBalanceForBtcFee()
updateUI()
return true
}

private val dialog by lazy {
indeterminateProgressDialog(message = R.string.Please_wait_a_bit).apply {
setCancelable(false)
dismiss()
}
}
}

Loading