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
15 changes: 3 additions & 12 deletions app/src/main/java/one/mixin/android/api/response/web3/ParsedTx.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ data class ParsedTx(
@SerializedName("code")
val code: Int? = null
) {
fun noBalanceChange(): Boolean = instructions?.isNotEmpty() == true && balanceChanges.isNullOrEmpty()
fun hasChanges(): Boolean = !balanceChanges.isNullOrEmpty() || !approves.isNullOrEmpty()

fun noBalanceChange(): Boolean = instructions?.isNotEmpty() == true && !hasChanges()
}

data class BalanceChange(
Expand Down Expand Up @@ -77,8 +79,6 @@ data class ParsedInstruction(
val instructionName: String,
@SerializedName("items")
val items: List<Item>? = null,
@SerializedName("token_changes")
val tokenChanges: List<TokenChange>? = null,
@SerializedName("info")
val info: String? = null,
)
Expand All @@ -89,12 +89,3 @@ data class Item(
@SerializedName("value")
val value: String
)

data class TokenChange(
@SerializedName("address")
val address: String,
@SerializedName("amount")
val amount: Long,
@SerializedName("is_pay")
val isPay: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package one.mixin.android.repository

import one.mixin.android.api.response.web3.BalanceChange
import one.mixin.android.db.web3.vo.AssetChange

internal data class PendingAssetChanges(
val senders: List<AssetChange>,
val receivers: List<AssetChange>,
)

internal fun pendingAssetChanges(balanceChanges: List<BalanceChange>?): PendingAssetChanges {
val senders = mutableListOf<AssetChange>()
val receivers = mutableListOf<AssetChange>()
balanceChanges.orEmpty().forEach { change ->
val amount = change.amount.toBigDecimalOrNull() ?: return@forEach
val assetChange = AssetChange(
assetId = change.assetId,
amount = amount.abs().toPlainString(),
from = change.from,
to = change.to,
)
when (amount.signum()) {
-1 -> senders.add(assetChange)
1 -> receivers.add(assetChange)
}
}
return PendingAssetChanges(senders, receivers)
}
51 changes: 12 additions & 39 deletions app/src/main/java/one/mixin/android/repository/TokenRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1160,32 +1160,11 @@ class TokenRepository
rate: BigDecimal?,
): Web3Transaction {
val resolvedAddress = gaslessPendingTransaction?.address ?: raw.account
val senders = mutableListOf<AssetChange>()
val receivers = mutableListOf<AssetChange>()
val pendingChanges = pendingAssetChanges(raw.simulateTx?.balanceChanges)
val senders = pendingChanges.senders
val receivers = pendingChanges.receivers
val approvals = mutableListOf<AssetChange>()

raw.simulateTx?.balanceChanges?.forEach { bc ->
val amt = bc.amount.toBigDecimalOrNull()
if (amt != null) {
receivers.add(
AssetChange(
assetId = bc.assetId,
amount = amt.abs().toPlainString(),
from = bc.from,
to = bc.to
)
)
senders.add(
AssetChange(
assetId = bc.assetId,
amount = amt.toPlainString(),
from = bc.from,
to = bc.to,
)
)
}
}

raw.simulateTx?.approves?.forEach { approve ->
approvals.add(
AssetChange(
Expand All @@ -1203,32 +1182,26 @@ class TokenRepository
val txType = when {
assetId in Constants.Web3UtxoChainIds -> TransactionType.TRANSFER_OUT.value
raw.simulateTx?.approves?.isNotEmpty() == true -> TransactionType.APPROVAL.value
(raw.simulateTx?.balanceChanges?.size ?: 0) > 1 -> TransactionType.SWAP.value
raw.simulateTx?.balanceChanges?.size == 1 -> TransactionType.TRANSFER_OUT.value
senders.isNotEmpty() && receivers.isNotEmpty() -> TransactionType.SWAP.value
senders.isNotEmpty() -> TransactionType.TRANSFER_OUT.value
receivers.isNotEmpty() -> TransactionType.TRANSFER_IN.value
else -> TransactionType.UNKNOWN.value
}

when (txType) {
TransactionType.SWAP.value -> {
raw.simulateTx?.balanceChanges?.forEach { bc ->
val amt = bc.amount.toBigDecimalOrNull()
if (amt != null) {
if (amt < BigDecimal.ZERO) {
sendAssetId = bc.assetId
} else if (amt > BigDecimal.ZERO) {
receiveAssetId = bc.assetId
}
}
}
sendAssetId = senders.firstOrNull()?.assetId
receiveAssetId = receivers.firstOrNull()?.assetId
}
TransactionType.TRANSFER_OUT.value -> {
raw.simulateTx?.balanceChanges?.firstOrNull {
it.amount.toBigDecimalOrNull()?.let { amt -> amt < BigDecimal.ZERO } == true
}?.let {
senders.firstOrNull()?.let {
sendAssetId = it.assetId
receiveAssetId = it.assetId
}
}
TransactionType.TRANSFER_IN.value -> {
receiveAssetId = receivers.firstOrNull()?.assetId
}
else -> {
sendAssetId = assetId
receiveAssetId = assetId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ package one.mixin.android.ui.common

import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import one.mixin.android.Constants
import one.mixin.android.compose.theme.MixinAppTheme
import one.mixin.android.extension.defaultSharedPreferences

internal enum class BalanceChangeTone {
POSITIVE,
Expand All @@ -26,7 +23,7 @@ internal fun balanceChangePresentation(
amount.withBalanceChangeSign()
} else {
val magnitude = amount.toBigDecimalOrNull()?.let { it.abs().toPlainString() }
?: amount.removePrefix("+").removePrefix("-")
?: amount.trimStart('+', '-')
if (isReceive) "+$magnitude" else "-$magnitude"
}
val tone = when (displayAmount.toBigDecimalOrNull()?.signum()) {
Expand All @@ -39,19 +36,9 @@ internal fun balanceChangePresentation(

@Composable
internal fun BalanceChangeTone.toColor(): Color {
val quoteColorReversed = LocalContext.current.defaultSharedPreferences
.getBoolean(Constants.Account.PREF_QUOTE_COLOR, false)
return when (this) {
BalanceChangeTone.POSITIVE -> if (quoteColorReversed) {
MixinAppTheme.colors.walletRed
} else {
MixinAppTheme.colors.walletGreen
}
BalanceChangeTone.NEGATIVE -> if (quoteColorReversed) {
MixinAppTheme.colors.walletGreen
} else {
MixinAppTheme.colors.walletRed
}
BalanceChangeTone.POSITIVE -> MixinAppTheme.colors.walletGreen
BalanceChangeTone.NEGATIVE -> MixinAppTheme.colors.walletRed
BalanceChangeTone.PLAIN -> MixinAppTheme.colors.textPrimary
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import one.mixin.android.Constants
import one.mixin.android.Constants.ChainId.ETHEREUM_CHAIN_ID
import one.mixin.android.Constants.ChainId.SOLANA_CHAIN_ID
import one.mixin.android.Constants.MIXIN_FREE_FEE
import one.mixin.android.Constants.RouteConfig.ROUTE_BOT_USER_ID
import one.mixin.android.MixinApplication
import one.mixin.android.R
import one.mixin.android.api.MixinResponse
Expand Down Expand Up @@ -50,7 +49,6 @@ import one.mixin.android.api.response.TransactionResponse
import one.mixin.android.api.response.getTransactionResult
import one.mixin.android.api.response.perps.PerpsMarket
import one.mixin.android.api.response.signature.SignatureAction
import one.mixin.android.api.response.web3.ParsedTx
import one.mixin.android.api.service.UtxoService
import one.mixin.android.crypto.CryptoWalletHelper
import one.mixin.android.crypto.PinCipher
Expand Down Expand Up @@ -1862,31 +1860,6 @@ class BottomSheetViewModel
}
}

suspend fun simulateWeb3Tx(tx: String, chainId: String, from: String?, to: String?): ParsedTx? {
var meet401 = false
var parsedTx: ParsedTx? = null
handleMixinResponse(
invokeNetwork = { tokenRepository.simulateWeb3Tx(Web3RawTransactionRequest(chainId, tx, from, to)) },
successBlock = { parsedTx = it.data },
failureBlock = {
if (it.errorCode == ErrorHandler.SIMULATE_TRANSACTION_FAILED) {
parsedTx = ParsedTx(code = ErrorHandler.SIMULATE_TRANSACTION_FAILED)
return@handleMixinResponse true
} else if (it.errorCode == 401) {
meet401 = true
return@handleMixinResponse true
}
return@handleMixinResponse false
}
)
if (parsedTx == null && meet401) {
userRepository.getBotPublicKey(ROUTE_BOT_USER_ID, true)
return simulateWeb3Tx(tx, chainId, from, to)
} else {
return parsedTx
}
}

suspend fun estimateFee(request: EstimateFeeRequest) = web3Repository.estimateFee(request)

suspend fun bindReferral(code: String) = userRepository.bindReferral(code)
Expand Down
12 changes: 10 additions & 2 deletions app/src/main/java/one/mixin/android/ui/home/web3/BrowserPage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -336,19 +336,27 @@ fun BrowserPage(
val customFeeValue = feeAmount?.toBigDecimalOrNull()
val feePrice = feeToken?.priceUsd?.toBigDecimalOrNull() ?: asset.priceUSD()
val fee = customFeeValue ?: tipGas?.displayValue(transaction?.maxFeePerGas) ?: solanaFee?.stripTrailingZeros()?: utxoFee?.stripTrailingZeros() ?: BigDecimal.ZERO
val isFeeLoading = step != WalletConnectBottomSheetDialogFragment.Step.Error && when {
transaction != null -> tipGas == null
chain == Chain.Solana && type == JsSignMessage.TYPE_RAW_TRANSACTION -> solanaFee == null
else -> false
}
val isFeeReady = step != WalletConnectBottomSheetDialogFragment.Step.Error && !isFeeLoading
if (fee == BigDecimal.ZERO) {
FeeInfo(
amount = "$fee",
fee = fee.multiply(feePrice),
isFree = isFeeWaived,
isFree = isFeeWaived && isFeeReady,
isLoading = isFeeLoading,
onFreeClick = onFreeClick,
)
} else {
FeeInfo(
amount = "$fee ${feeToken?.symbol ?: asset?.symbol ?: ""}",
fee = fee.multiply(feePrice),
gasPrice = tipGas?.displayGas(transaction?.maxFeePerGas)?.toPlainString(),
isFree = isFeeWaived,
isFree = isFeeWaived && isFeeReady,
isLoading = isFeeLoading,
onFreeClick = onFreeClick,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ class BrowserWalletBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag
const val ARGS_IS_FEE_FREE = "args_is_fee_free"
const val ARGS_FEE_AMOUNT = "args_fee_amount"
const val ARGS_FEE_TOKEN = "args_fee_token"
const val ARGS_TIP_GAS_LIMIT = "args_tip_gas_limit"
const val ARGS_TIP_GAS_MAX_FEE_PER_GAS = "args_tip_gas_max_fee_per_gas"
const val ARGS_TIP_GAS_MAX_PRIORITY_FEE_PER_GAS = "args_tip_gas_max_priority_fee_per_gas"

fun newInstance(
jsSignMessage: JsSignMessage,
Expand All @@ -111,6 +114,7 @@ class BrowserWalletBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag
toAddress: String? = null,
toUser: User? = null,
isFeeWaived: Boolean = false,
tipGas: TipGas? = null,
) = BrowserWalletBottomSheetDialogFragment().withArgs {
putParcelable(ARGS_MESSAGE, jsSignMessage)
putString(
Expand All @@ -128,6 +132,11 @@ class BrowserWalletBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag
toAddress?.let { putString(ARGS_TO_ADDRESS, it) }
toUser?.let { putParcelable(ARGS_TO_USER, it) }
putBoolean(ARGS_IS_FEE_FREE, isFeeWaived)
tipGas?.let {
putString(ARGS_TIP_GAS_LIMIT, it.gasLimit.toString())
putString(ARGS_TIP_GAS_MAX_FEE_PER_GAS, it.maxFeePerGas.toString())
putString(ARGS_TIP_GAS_MAX_PRIORITY_FEE_PER_GAS, it.maxPriorityFeePerGas.toString())
}
}
}

Expand All @@ -153,6 +162,13 @@ class BrowserWalletBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag
requireArguments().getParcelableCompat(ARGS_FEE_TOKEN, Web3TokenItem::class.java)
}
private val isFeeWaived by lazy { requireArguments().getBoolean(ARGS_IS_FEE_FREE, false) }
private val initialTipGas by lazy {
val args = requireArguments()
val gasLimit = args.getString(ARGS_TIP_GAS_LIMIT)?.toBigIntegerOrNull() ?: return@lazy null
val maxFeePerGas = args.getString(ARGS_TIP_GAS_MAX_FEE_PER_GAS)?.toBigIntegerOrNull() ?: return@lazy null
val maxPriorityFeePerGas = args.getString(ARGS_TIP_GAS_MAX_PRIORITY_FEE_PER_GAS)?.toBigIntegerOrNull() ?: return@lazy null
TipGas(currentChain.chainId, gasLimit, maxFeePerGas, maxPriorityFeePerGas)
}
private val currentChain by lazy {
token?.getChainFromName() ?: Web3Signer.currentChain
}
Expand Down Expand Up @@ -182,6 +198,7 @@ class BrowserWalletBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag
super.onViewCreated(view, savedInstanceState)
token = requireArguments().getParcelableCompat(ARGS_TOKEN, Web3TokenItem::class.java)
amount = requireArguments().getString(ARGS_AMOUNT)
tipGas = initialTipGas
if (isAccountUnavailable()) {
val message = getString(R.string.not_support_network, currentChain.symbol)
settleError(WalletErrorCode.UNSUPPORTED_METHOD, message)
Expand Down Expand Up @@ -315,38 +332,46 @@ class BrowserWalletBottomSheetDialogFragment : MixinComposeBottomSheetDialogFrag
}
val assetId = chain.getWeb3ChainId()
val transaction = signMessage.wcEthereumTransaction ?: return
val cachedTipGas = tipGas
var useCachedTipGas = cachedTipGas != null
tickerFlow(15.seconds)
.onEach {
asset = viewModel.refreshAsset(assetId)
try {
tipGas = withContext(Dispatchers.IO) {
val r = runCatching {
viewModel.estimateFee(
EstimateFeeRequest(
assetId,
null,
transaction.data,
transaction.from,
transaction.to,
transaction.value,
val currentTipGas = if (useCachedTipGas) {
useCachedTipGas = false
cachedTipGas
} else {
withContext(Dispatchers.IO) {
val r = runCatching {
viewModel.estimateFee(
EstimateFeeRequest(
assetId,
null,
transaction.data,
transaction.from,
transaction.to,
transaction.value,
)
)
)
}.getOrNull()
if (r?.isSuccess != true) {
step = Step.Error
ErrorHandler.handleMixinError(r?.errorCode ?: 0, r?.errorDescription ?: "")
return@withContext null
}.getOrNull()
if (r?.isSuccess != true) {
step = Step.Error
ErrorHandler.handleMixinError(r?.errorCode ?: 0, r?.errorDescription ?: "")
return@withContext null
}
buildTipGas(chain.chainId, r.data!!)
}
buildTipGas(chain.chainId, r.data!!)
} ?: return@onEach
insufficientGas = checkGas(token, chainToken, tipGas, transaction.value, transaction.maxFeePerGas)
tipGas = currentTipGas
insufficientGas = checkGas(token, chainToken, currentTipGas, transaction.value, transaction.maxFeePerGas)
if (insufficientGas) {
handleException(IllegalArgumentException(requireContext().getString(R.string.insufficient_gas, chainToken?.symbol ?: currentChain.symbol)))
}
val hex = Web3Signer.ethPreviewTransaction(
Web3Signer.evmAddress,
transaction,
tipGas!!,
currentTipGas,
chain = token?.getChainFromName()
) { _ ->
val nonce = rpc.nonceAt(currentChain.assetId, Web3Signer.evmAddress) ?: throw IllegalArgumentException("failed to get nonce")
Expand Down
Loading