Fixes approve/reject dialog for Solana transactions on Android

This commit is contained in:
Serg
2022-08-03 15:47:45 -04:00
parent 7bd91cdca5
commit 8f12e030f3
10 changed files with 181 additions and 51 deletions
@@ -36,13 +36,14 @@ public class ApproveTxFragmentPageAdapter extends FragmentStatePagerAdapter {
private HashMap<String, Double> mNativeAssetsBalances;
private HashMap<String, HashMap<String, Double>> mBlockchainTokensBalances;
private boolean mUpdateTxObjectManually;
private long mSolanaEstimatedTxFee;
public ApproveTxFragmentPageAdapter(FragmentManager fm, TransactionInfo txInfo,
NetworkInfo selectedNetwork, AccountInfo[] accounts,
HashMap<String, Double> assetPrices, BlockchainToken[] fullTokenList,
HashMap<String, Double> nativeAssetsBalances,
HashMap<String, HashMap<String, Double>> blockchainTokensBalances, Activity activity,
boolean updateTxObjectManually) {
boolean updateTxObjectManually, long solanaEstimatedTxFee) {
super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
mTxInfo = txInfo;
mSelectedNetwork = selectedNetwork;
@@ -54,6 +55,7 @@ public class ApproveTxFragmentPageAdapter extends FragmentStatePagerAdapter {
mTitles = new ArrayList<>(Arrays.asList(activity.getText(R.string.transaction).toString(),
activity.getText(R.string.transaction_details).toString()));
mUpdateTxObjectManually = updateTxObjectManually;
mSolanaEstimatedTxFee = solanaEstimatedTxFee;
}
@NonNull
@@ -62,7 +64,7 @@ public class ApproveTxFragmentPageAdapter extends FragmentStatePagerAdapter {
if (position == 0) {
return TxFragment.newInstance(mTxInfo, mSelectedNetwork, mAccounts, mAssetPrices,
mFullTokenList, mNativeAssetsBalances, mBlockchainTokensBalances,
mUpdateTxObjectManually);
mUpdateTxObjectManually, mSolanaEstimatedTxFee);
} else {
return TxDetailsFragment.newInstance(mTxInfo);
}
@@ -49,6 +49,7 @@ import org.chromium.chrome.browser.crypto_wallet.adapters.ApproveTxFragmentPageA
import org.chromium.chrome.browser.crypto_wallet.listeners.TransactionConfirmationListener;
import org.chromium.chrome.browser.crypto_wallet.observers.ApprovedTxObserver;
import org.chromium.chrome.browser.crypto_wallet.util.ParsedTransaction;
import org.chromium.chrome.browser.crypto_wallet.util.SolanaTransactionsGasHelper;
import org.chromium.chrome.browser.crypto_wallet.util.TokenUtils;
import org.chromium.chrome.browser.crypto_wallet.util.TransactionUtils;
import org.chromium.chrome.browser.crypto_wallet.util.Utils;
@@ -77,6 +78,7 @@ public class ApproveTxBottomSheetDialogFragment extends BottomSheetDialogFragmen
private List<TransactionInfo> mTransactionInfos;
private Button mRejectAllTx;
private int mCoinType;
private long mSolanaEstimatedTxFee;
public static ApproveTxBottomSheetDialogFragment newInstance(
List<TransactionInfo> transactionInfos, TransactionInfo txInfo, String accountName,
@@ -102,6 +104,7 @@ public class ApproveTxBottomSheetDialogFragment extends BottomSheetDialogFragmen
// TODO (Wengling): To support other networks, all hard-coded chainSymbol, etc. need to be
// get from current network instead.
mTransactionInfos = Collections.emptyList();
mSolanaEstimatedTxFee = 0;
}
ApproveTxBottomSheetDialogFragment(List<TransactionInfo> transactionInfos,
@@ -237,31 +240,44 @@ public class ApproveTxBottomSheetDialogFragment extends BottomSheetDialogFragmen
TokenUtils.getAllTokensFiltered(getBraveWalletService(), getBlockchainRegistry(),
selectedNetwork, selectedNetwork.coin, TokenUtils.TokenType.ALL,
tokenList -> {
ParsedTransaction parsedTx = fillAssetDependentControls(view,
selectedNetwork, accounts, new HashMap<String, Double>(),
tokenList, new HashMap<String, Double>(),
new HashMap<String, HashMap<String, Double>>());
SolanaTransactionsGasHelper solanaTransactionsGasHelper =
new SolanaTransactionsGasHelper(
(BraveWalletBaseActivity) getActivity(),
new TransactionInfo[] {mTxInfo});
solanaTransactionsGasHelper.maybeGetSolanaGasEstimations(() -> {
HashMap<String, Long> perTxFee =
solanaTransactionsGasHelper.getPerTxFee();
if (perTxFee.get(mTxInfo.id) != null) {
mSolanaEstimatedTxFee = perTxFee.get(mTxInfo.id);
}
ParsedTransaction parsedTx = fillAssetDependentControls(view,
selectedNetwork, accounts, new HashMap<String, Double>(),
tokenList, new HashMap<String, Double>(),
new HashMap<String, HashMap<String, Double>>(),
mSolanaEstimatedTxFee);
// Get tokens involved in this transaction
List<BlockchainToken> tokens = new ArrayList<>();
tokens.add(Utils.makeNetworkAsset(
selectedNetwork)); // Always add native asset
if (parsedTx.getIsSwap()) {
tokens.add(parsedTx.getSellToken());
tokens.add(parsedTx.getBuyToken());
} else if (parsedTx.getToken() != null)
tokens.add(parsedTx.getToken());
BlockchainToken[] filterByTokens =
tokens.toArray(new BlockchainToken[0]);
// Get tokens involved in this transaction
List<BlockchainToken> tokens = new ArrayList<>();
tokens.add(Utils.makeNetworkAsset(
selectedNetwork)); // Always add native asset
if (parsedTx.getIsSwap()) {
tokens.add(parsedTx.getSellToken());
tokens.add(parsedTx.getBuyToken());
} else if (parsedTx.getToken() != null)
tokens.add(parsedTx.getToken());
BlockchainToken[] filterByTokens =
tokens.toArray(new BlockchainToken[0]);
Utils.getTxExtraInfo((BraveWalletBaseActivity) getActivity(),
selectedNetwork, accounts, filterByTokens, false,
(assetPrices, fullTokenList, nativeAssetsBalances,
blockchainTokensBalances) -> {
fillAssetDependentControls(view, selectedNetwork, accounts,
assetPrices, fullTokenList, nativeAssetsBalances,
blockchainTokensBalances);
});
Utils.getTxExtraInfo((BraveWalletBaseActivity) getActivity(),
selectedNetwork, accounts, filterByTokens, false,
(assetPrices, fullTokenList, nativeAssetsBalances,
blockchainTokensBalances) -> {
fillAssetDependentControls(view, selectedNetwork,
accounts, assetPrices, fullTokenList,
nativeAssetsBalances, blockchainTokensBalances,
mSolanaEstimatedTxFee);
});
});
});
});
});
@@ -327,10 +343,11 @@ public class ApproveTxBottomSheetDialogFragment extends BottomSheetDialogFragmen
private ParsedTransaction fillAssetDependentControls(View view, NetworkInfo selectedNetwork,
AccountInfo[] accounts, HashMap<String, Double> assetPrices,
BlockchainToken[] fullTokenList, HashMap<String, Double> nativeAssetsBalances,
HashMap<String, HashMap<String, Double>> blockchainTokensBalances) {
ParsedTransaction parsedTx =
ParsedTransaction.parseTransaction(mTxInfo, selectedNetwork, accounts, assetPrices,
null, fullTokenList, nativeAssetsBalances, blockchainTokensBalances);
HashMap<String, HashMap<String, Double>> blockchainTokensBalances,
long solanaEstimatedTxFee) {
ParsedTransaction parsedTx = ParsedTransaction.parseTransaction(mTxInfo, selectedNetwork,
accounts, assetPrices, solanaEstimatedTxFee, fullTokenList, nativeAssetsBalances,
blockchainTokensBalances);
TextView txType = view.findViewById(R.id.tx_type);
if (parsedTx.getType() == TransactionType.ERC20_APPROVE) {
txType.setText(String.format(
@@ -346,7 +363,7 @@ public class ApproveTxBottomSheetDialogFragment extends BottomSheetDialogFragmen
parsedTx.formatValueToDisplay(), parsedTx.getSymbol());
TextView fromTo = view.findViewById(R.id.from_to);
fromTo.setText(String.format(getResources().getString(R.string.crypto_wallet_from_to),
mAccountName, parsedTx.getSenderLabel(), parsedTx.getRecipientLabel()));
mAccountName, parsedTx.getSender(), parsedTx.getRecipient()));
TextView amountAsset = view.findViewById(R.id.amount_asset);
TextView amountFiat = view.findViewById(R.id.amount_fiat);
amountFiat.setText(
@@ -371,7 +388,7 @@ public class ApproveTxBottomSheetDialogFragment extends BottomSheetDialogFragmen
ApproveTxFragmentPageAdapter adapter = new ApproveTxFragmentPageAdapter(
getChildFragmentManager(), mTxInfo, selectedNetwork, accounts, assetPrices,
fullTokenList, nativeAssetsBalances, blockchainTokensBalances, getActivity(),
mTransactionConfirmationListener == null);
mTransactionConfirmationListener == null, mSolanaEstimatedTxFee);
viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(adapter.getCount() - 1);
TabLayout tabLayout = view.findViewById(R.id.tabs);
@@ -56,6 +56,7 @@ public class TxFragment extends Fragment {
private HashMap<String, HashMap<String, Double>> mBlockchainTokensBalances;
private int mCheckedPriorityId;
private int mPreviousCheckedPriorityId;
private long mSolanaEstimatedTxFee;
// mUpdateTxObjectManually is used to detect do we need to update dialog values
// manually after we change gas for example or do we have it updated automatically
@@ -68,9 +69,10 @@ public class TxFragment extends Fragment {
AccountInfo[] accounts, HashMap<String, Double> assetPrices,
BlockchainToken[] fullTokenList, HashMap<String, Double> nativeAssetsBalances,
HashMap<String, HashMap<String, Double>> blockchainTokensBalances,
boolean updateTxObjectManually) {
boolean updateTxObjectManually, long solanaEstimatedTxFee) {
return new TxFragment(txInfo, selectedNetwork, accounts, assetPrices, fullTokenList,
nativeAssetsBalances, blockchainTokensBalances, updateTxObjectManually);
nativeAssetsBalances, blockchainTokensBalances, updateTxObjectManually,
solanaEstimatedTxFee);
}
private EthTxManagerProxy getEthTxManagerProxy() {
@@ -85,7 +87,7 @@ public class TxFragment extends Fragment {
HashMap<String, Double> assetPrices, BlockchainToken[] fullTokenList,
HashMap<String, Double> nativeAssetsBalances,
HashMap<String, HashMap<String, Double>> blockchainTokensBalances,
boolean updateTxObjectManually) {
boolean updateTxObjectManually, long solanaEstimatedTxFee) {
mTxInfo = txInfo;
mSelectedNetwork = selectedNetwork;
mAccounts = accounts;
@@ -96,6 +98,7 @@ public class TxFragment extends Fragment {
mCheckedPriorityId = -1;
mPreviousCheckedPriorityId = -1;
mUpdateTxObjectManually = updateTxObjectManually;
mSolanaEstimatedTxFee = solanaEstimatedTxFee;
}
@Override
@@ -390,7 +393,7 @@ public class TxFragment extends Fragment {
final double[] gasFeeArr = ParsedTransactionFees.calcGasFee(mSelectedNetwork,
Utils.getOrDefault(mAssetPrices,
mSelectedNetwork.symbol.toLowerCase(Locale.getDefault()), 0.0d),
true, gasLimit, "0", maxFeePerGas, false, null);
true, gasLimit, "0", maxFeePerGas, false, mSolanaEstimatedTxFee);
textView.setText(String.format(getResources().getString(R.string.wallet_maximum_fee),
String.format(Locale.getDefault(), "%.2f", gasFeeArr[1]),
String.format(Locale.getDefault(), "%.8f", gasFeeArr[0])));
@@ -398,8 +401,10 @@ public class TxFragment extends Fragment {
private void setupView(View view) {
// Re-parse transaction for mUpdateTxObjectManually
// TODO(sergz): Not really sure do we need to re-parse here as we parse it in the
// parent fragment
mParsedTx = ParsedTransaction.parseTransaction(mTxInfo, mSelectedNetwork, mAccounts,
mAssetPrices, null, mFullTokenList, mNativeAssetsBalances,
mAssetPrices, mSolanaEstimatedTxFee, mFullTokenList, mNativeAssetsBalances,
mBlockchainTokensBalances);
TextView gasFeeAmount = view.findViewById(R.id.gas_fee_amount);
@@ -14,6 +14,7 @@ import org.chromium.brave_wallet.mojom.BlockchainToken;
import org.chromium.brave_wallet.mojom.BraveWalletService;
import org.chromium.brave_wallet.mojom.JsonRpcService;
import org.chromium.brave_wallet.mojom.NetworkInfo;
import org.chromium.brave_wallet.mojom.SolanaTxManagerProxy;
import org.chromium.brave_wallet.mojom.TransactionInfo;
import org.chromium.brave_wallet.mojom.TxService;
import org.chromium.mojo.bindings.Callbacks;
@@ -304,4 +305,24 @@ public class AsyncUtils {
super.fireResponseCompleteCallback();
}
}
public static class GetSolanaEstimatedTxFeeResponseContext extends SingleResponseBaseContext
implements SolanaTxManagerProxy.GetEstimatedTxFee_Response {
public Long fee;
public Integer error;
public String errorMessage;
public String txMetaId;
public GetSolanaEstimatedTxFeeResponseContext(Runnable responseCompleteCallback) {
super(responseCompleteCallback);
}
@Override
public void call(Long fee, Integer error, String errorMessage) {
this.fee = fee;
this.error = error;
this.errorMessage = errorMessage;
super.fireResponseCompleteCallback();
}
}
}
@@ -242,7 +242,7 @@ public class ParsedTransaction extends ParsedTransactionFees {
public static ParsedTransaction parseTransaction(TransactionInfo txInfo,
NetworkInfo selectedNetwork, AccountInfo[] accounts,
HashMap<String, Double> assetPrices, BigInteger solFeeEstimatesFee,
HashMap<String, Double> assetPrices, long solFeeEstimatesFee,
BlockchainToken[] fullTokenList, HashMap<String, Double> nativeAssetsBalances,
HashMap<String, HashMap<String, Double>> blockchainTokensBalances) {
BlockchainToken nativeAsset = Utils.makeNetworkAsset(selectedNetwork);
@@ -398,21 +398,21 @@ public class ParsedTransaction extends ParsedTransactionFees {
.SOLANA_SPL_TOKEN_TRANSFER_WITH_ASSOCIATED_TOKEN_ACCOUNT_CREATION) {
final int decimals = token != null ? token.decimals : Utils.SOL_DEFAULT_DECIMALS;
final double price = Utils.getOrDefault(assetPrices, tokenSymbolLower, 0.0d);
final double sendAmountFiat = Utils.fromHexWei(value, decimals) * price;
final double sendAmount = Utils.fromWei(value, decimals);
final double sendAmountFiat = sendAmount * price;
final double totalAmountFiat = parsedTransaction.getGasFeeFiat() + sendAmountFiat;
final boolean insufficientNativeFunds =
parsedTransaction.getGasFee() > accountNativeBalance;
final boolean insufficientTokenFunds =
Utils.fromHexWei(value, decimals) > accountTokenBalance;
final boolean insufficientTokenFunds = sendAmount > accountTokenBalance;
parsedTransaction.recipient = to;
parsedTransaction.recipientLabel = getAddressLabel(accounts, to);
parsedTransaction.fiatValue = sendAmountFiat;
parsedTransaction.fiatTotal = totalAmountFiat;
parsedTransaction.nativeCurrencyTotal = sendAmountFiat / networkSpotPrice;
parsedTransaction.value = Utils.fromHexWei(value, decimals);
parsedTransaction.value = sendAmount;
parsedTransaction.symbol = token != null ? token.symbol : "";
parsedTransaction.decimals = decimals;
parsedTransaction.insufficientFundsError = insufficientTokenFunds;
@@ -484,8 +484,14 @@ public class ParsedTransaction extends ParsedTransactionFees {
for (String k : assetPrices.keySet()) {
String v = String.valueOf(assetPrices.get(k));
}
double sendAmount = 0;
if (txInfo.txType == TransactionType.SOLANA_SYSTEM_TRANSFER) {
sendAmount = Utils.fromWei(value, selectedNetwork.decimals);
} else {
sendAmount = Utils.fromHexWei(value, selectedNetwork.decimals);
}
final double sendAmountFiat = Utils.fromHexWei(value, selectedNetwork.decimals) * price;
final double sendAmountFiat = sendAmount * price;
final double totalAmountFiat = parsedTransaction.getGasFeeFiat() + sendAmountFiat;
@@ -494,7 +500,7 @@ public class ParsedTransaction extends ParsedTransactionFees {
parsedTransaction.fiatValue = sendAmountFiat;
parsedTransaction.fiatTotal = totalAmountFiat;
parsedTransaction.nativeCurrencyTotal = sendAmountFiat / networkSpotPrice;
parsedTransaction.value = Utils.fromHexWei(value, selectedNetwork.decimals);
parsedTransaction.value = sendAmount;
parsedTransaction.symbol = selectedNetwork.symbol;
parsedTransaction.decimals = selectedNetwork.decimals;
parsedTransaction.insufficientFundsError =
@@ -15,7 +15,6 @@ import org.chromium.brave_wallet.mojom.TxData;
import org.chromium.brave_wallet.mojom.TxData1559;
import org.chromium.brave_wallet.mojom.TxDataUnion;
import java.math.BigInteger;
import java.util.Locale;
/*
@@ -98,7 +97,7 @@ public class ParsedTransactionFees {
}
public static ParsedTransactionFees parseTransactionFees(TransactionInfo txInfo,
NetworkInfo selectedNetwork, Double networkSpotPrice, BigInteger solFeeEstimatesFee) {
NetworkInfo selectedNetwork, Double networkSpotPrice, long solFeeEstimatesFee) {
TxDataUnion txDataUnion = txInfo.txDataUnion;
TxData1559 txData = txDataUnion.which() == TxDataUnion.Tag.EthTxData1559
? txDataUnion.getEthTxData1559()
@@ -141,11 +140,11 @@ public class ParsedTransactionFees {
// so extracting this part as a separate function
public static double[] calcGasFee(NetworkInfo selectedNetwork, double networkSpotPrice,
boolean isEIP1559Transaction, String gasLimit, String gasPrice, String maxFeePerGas,
boolean isSolTransaction, BigInteger solFeeEstimatesFee) {
boolean isSolTransaction, long solFeeEstimatesFee) {
final int networkDecimals = selectedNetwork.decimals;
final double gasFee = isSolTransaction
? solFeeEstimatesFee != null
? Utils.fromHexWei(solFeeEstimatesFee.toString(), networkDecimals)
? solFeeEstimatesFee != 0
? Utils.fromWei(Long.toString(solFeeEstimatesFee), networkDecimals)
: 0.0d
: Utils.fromHexWei(isEIP1559Transaction
? Utils.multiplyHexBN(maxFeePerGas, gasLimit)
@@ -0,0 +1,79 @@
/* Copyright (c) 2022 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.chromium.chrome.browser.crypto_wallet.util;
import org.chromium.brave_wallet.mojom.SolanaProviderError;
import org.chromium.brave_wallet.mojom.TransactionInfo;
import org.chromium.brave_wallet.mojom.TransactionType;
import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletBaseActivity;
import org.chromium.chrome.browser.crypto_wallet.util.AsyncUtils;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
public class SolanaTransactionsGasHelper {
private final WeakReference<BraveWalletBaseActivity> mActivity;
private TransactionInfo[] mTransactionInfos;
private HashMap<String, Long> mPerTxFee;
public SolanaTransactionsGasHelper(
BraveWalletBaseActivity activity, TransactionInfo[] transactionInfos) {
mActivity = new WeakReference<BraveWalletBaseActivity>(activity);
mTransactionInfos = transactionInfos;
mPerTxFee = new HashMap<String, Long>();
}
public HashMap<String, Long> getPerTxFee() {
return mPerTxFee;
}
public void maybeGetSolanaGasEstimations(Runnable runWhenDone) {
// Filter out everything that's not related to Solana
ArrayList<TransactionInfo> solanaTransactions = new ArrayList<TransactionInfo>();
for (TransactionInfo txInfo : mTransactionInfos) {
if (txInfo.txType == TransactionType.SOLANA_SYSTEM_TRANSFER
|| txInfo.txType == TransactionType.SOLANA_SPL_TOKEN_TRANSFER
|| txInfo.txType
== TransactionType
.SOLANA_SPL_TOKEN_TRANSFER_WITH_ASSOCIATED_TOKEN_ACCOUNT_CREATION
|| txInfo.txType == TransactionType.SOLANA_DAPP_SIGN_AND_SEND_TRANSACTION) {
solanaTransactions.add(txInfo);
}
}
AsyncUtils.MultiResponseHandler estimatesMultiResponse =
new AsyncUtils.MultiResponseHandler(solanaTransactions.size());
ArrayList<AsyncUtils.GetSolanaEstimatedTxFeeResponseContext> estimatesContexts =
new ArrayList<AsyncUtils.GetSolanaEstimatedTxFeeResponseContext>();
for (TransactionInfo txInfo : solanaTransactions) {
AsyncUtils.GetSolanaEstimatedTxFeeResponseContext estimatesContext =
new AsyncUtils.GetSolanaEstimatedTxFeeResponseContext(
estimatesMultiResponse.singleResponseComplete);
estimatesContext.txMetaId = txInfo.id;
estimatesContexts.add(estimatesContext);
if (mActivity != null)
mActivity.get().getSolanaTxManagerProxy().getEstimatedTxFee(
txInfo.id, estimatesContext);
}
estimatesMultiResponse.setWhenAllCompletedAction(() -> {
for (AsyncUtils.GetSolanaEstimatedTxFeeResponseContext estimatesContext :
estimatesContexts) {
if (estimatesContext.error != SolanaProviderError.SUCCESS) {
continue;
}
mPerTxFee.put(estimatesContext.txMetaId, estimatesContext.fee);
}
runWhenDone.run();
});
}
}
@@ -636,7 +636,7 @@ public class Utils {
if (integerPlaces != -1 && (integerPlaces + 9) <= resStr.length()) {
resStr = resStr.substring(0, integerPlaces + 9);
}
return Long.parseLong(resStr);
return (long) Double.parseDouble(resStr);
} catch (Exception e) {
e.printStackTrace();
}
@@ -1179,7 +1179,7 @@ public class Utils {
TransactionInfo[] txInfos = pendingTxInfos.get(accountName);
for (TransactionInfo txInfo : txInfos) {
ParsedTransaction parsedTx = ParsedTransaction.parseTransaction(txInfo,
selectedNetwork, accounts, assetPrices, null, fullTokenList,
selectedNetwork, accounts, assetPrices, 0, fullTokenList,
nativeAssetsBalances, blockchainTokensBalances);
WalletListItemModel itemModel =
makeWalletItem((Context) activity, txInfo, selectedNetwork, parsedTx);
@@ -142,7 +142,7 @@
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/amount_asset" />
app:layout_constraintTop_toBottomOf="@id/amount_fiat" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"