From 1197d6a0908ae80c26c42be8ef425003fa0a7299 Mon Sep 17 00:00:00 2001 From: Pavneet-Sing Date: Fri, 4 Nov 2022 15:56:57 +0530 Subject: [PATCH] Uplift of #15593 (squashed) to beta --- android/brave_java_sources.gni | 4 +- android/java/AndroidManifest.xml | 2 +- .../activities/BraveWalletDAppsActivity.java | 5 + .../ApproveTxFragmentPageAdapter.java | 21 +- .../TwoLineItemRecyclerViewAdapter.java | 4 + .../ApproveTxBottomSheetDialogFragment.java | 44 +- .../fragments/SolanaTxDetailsFragment.java | 39 ++ .../fragments/TwoLineItemFragment.java | 20 + .../fragments/TxDetailsFragment.java | 31 +- .../crypto_wallet/fragments/TxFragment.java | 70 +++- .../dapps/SignTransactionFragment.java | 2 +- .../crypto_wallet/modal/BraveWalletPanel.java | 2 +- .../crypto_wallet/presenters/Amount.java | 79 ++++ .../SolanaInstructionPresenter.java | 86 +++- .../crypto_wallet/util/AndroidUtils.java | 35 ++ .../crypto_wallet/util/ParsedTransaction.java | 395 +++++++++++------- .../util/ParsedTransactionFees.java | 8 +- .../util/SolanaTransactionsGasHelper.java | 2 +- .../crypto_wallet/util/TransactionUtils.java | 39 +- .../crypto_wallet/util/WalletConstants.java | 18 +- .../res/layout/approve_tx_bottom_sheet.xml | 26 +- .../java/res/layout/fragment_transaction.xml | 16 +- .../layout/item_fragment_two_line_item.xml | 3 +- .../java/res/values-night/brave_colors.xml | 1 + android/java/res/values/brave_colors.xml | 1 + android/java/res/values/brave_styles.xml | 25 ++ .../android/strings/android_brave_strings.grd | 41 +- .../browser/solana_provider_impl.cc | 2 + 28 files changed, 783 insertions(+), 238 deletions(-) create mode 100644 android/java/org/chromium/chrome/browser/crypto_wallet/fragments/SolanaTxDetailsFragment.java create mode 100644 android/java/org/chromium/chrome/browser/crypto_wallet/presenters/Amount.java rename android/java/org/chromium/chrome/browser/crypto_wallet/{model => presenters}/SolanaInstructionPresenter.java (70%) diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni index 25eee42375629..bfb91486639ba 100644 --- a/android/brave_java_sources.gni +++ b/android/brave_java_sources.gni @@ -107,6 +107,7 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/CreateAccountBottomSheetFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/EditVisibleAssetsBottomSheetDialogFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/PortfolioFragment.java", + "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/SolanaTxDetailsFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/SwapBottomSheetDialogFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TwoLineItemFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxDetailsFragment.java", @@ -135,7 +136,6 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/modal/DAppsDialog.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/model/CryptoAccountTypeInfo.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/model/OnboardingViewModel.java", - "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/model/SolanaInstructionPresenter.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/model/TxNonSwipeableViewPager.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/model/WalletListItemModel.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/observers/ApprovedTxObserver.java", @@ -143,6 +143,8 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/observers/TxServiceObserverImpl.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/permission/BraveDappPermissionPromptDialog.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/permission/BravePermissionAccountsListAdapter.java", + "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/presenters/Amount.java", + "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/presenters/SolanaInstructionPresenter.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/util/AccountsPermissionsHelper.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/util/AddressUtils.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java", diff --git a/android/java/AndroidManifest.xml b/android/java/AndroidManifest.xml index 77f19cf32e16c..cd3bb3ef80859 100644 --- a/android/java/AndroidManifest.xml +++ b/android/java/AndroidManifest.xml @@ -97,7 +97,7 @@ tools:ignore="LockedOrientationActivity"/> diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletDAppsActivity.java b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletDAppsActivity.java index cee111cebe77d..e62f0223731d7 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletDAppsActivity.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/activities/BraveWalletDAppsActivity.java @@ -9,6 +9,8 @@ import android.content.Intent; import android.util.Log; +import android.view.Window; +import android.view.WindowManager; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentTransaction; @@ -84,6 +86,9 @@ public int getValue() { @Override protected void triggerLayoutInflation() { + requestWindowFeature(Window.FEATURE_NO_TITLE); + this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_brave_wallet_dapps); Intent intent = getIntent(); mActivityType = ActivityType.valueOf( diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/ApproveTxFragmentPageAdapter.java b/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/ApproveTxFragmentPageAdapter.java index 9eff88cff9cf2..33532a8a6acdf 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/ApproveTxFragmentPageAdapter.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/ApproveTxFragmentPageAdapter.java @@ -6,6 +6,7 @@ package org.chromium.chrome.browser.crypto_wallet.adapters; import android.app.Activity; +import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -18,8 +19,10 @@ import org.chromium.brave_wallet.mojom.NetworkInfo; import org.chromium.brave_wallet.mojom.TransactionInfo; import org.chromium.chrome.R; +import org.chromium.chrome.browser.crypto_wallet.fragments.SolanaTxDetailsFragment; import org.chromium.chrome.browser.crypto_wallet.fragments.TxDetailsFragment; import org.chromium.chrome.browser.crypto_wallet.fragments.TxFragment; +import org.chromium.chrome.browser.crypto_wallet.util.TransactionUtils; import java.util.ArrayList; import java.util.Arrays; @@ -37,6 +40,8 @@ public class ApproveTxFragmentPageAdapter extends FragmentStatePagerAdapter { private HashMap> mBlockchainTokensBalances; private boolean mUpdateTxObjectManually; private long mSolanaEstimatedTxFee; + private Context mContext; + private Fragment mDetailsFragment; public ApproveTxFragmentPageAdapter(FragmentManager fm, TransactionInfo txInfo, NetworkInfo selectedNetwork, AccountInfo[] accounts, @@ -52,6 +57,7 @@ public ApproveTxFragmentPageAdapter(FragmentManager fm, TransactionInfo txInfo, mFullTokenList = fullTokenList; mNativeAssetsBalances = nativeAssetsBalances; mBlockchainTokensBalances = blockchainTokensBalances; + mContext = activity; mTitles = new ArrayList<>(Arrays.asList(activity.getText(R.string.transaction).toString(), activity.getText(R.string.transaction_details).toString())); mUpdateTxObjectManually = updateTxObjectManually; @@ -66,10 +72,23 @@ public Fragment getItem(int position) { mFullTokenList, mNativeAssetsBalances, mBlockchainTokensBalances, mUpdateTxObjectManually, mSolanaEstimatedTxFee); } else { - return TxDetailsFragment.newInstance(mTxInfo); + if (mDetailsFragment == null) { + if (TransactionUtils.isSolanaTx(mTxInfo)) { + mDetailsFragment = SolanaTxDetailsFragment.newInstance(mTxInfo, mContext); + } else { + mDetailsFragment = TxDetailsFragment.newInstance(mTxInfo); + } + } + return mDetailsFragment; } } + @Override + public void notifyDataSetChanged() { + mDetailsFragment = null; + super.notifyDataSetChanged(); + } + @Override public int getCount() { return mTitles.size(); diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/TwoLineItemRecyclerViewAdapter.java b/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/TwoLineItemRecyclerViewAdapter.java index 98faa84eda1b4..d20634071212d 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/TwoLineItemRecyclerViewAdapter.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/adapters/TwoLineItemRecyclerViewAdapter.java @@ -107,6 +107,10 @@ public TwoLineItemText(String title, String subTitle) { this.subTitle = subTitle; } + public TwoLineItemText(String title) { + this.title = title; + } + public String getTitle() { return title; } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/ApproveTxBottomSheetDialogFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/ApproveTxBottomSheetDialogFragment.java index 3a02582898f7c..3fa268034da83 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/ApproveTxBottomSheetDialogFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/ApproveTxBottomSheetDialogFragment.java @@ -48,6 +48,7 @@ import org.chromium.chrome.browser.crypto_wallet.adapters.ApproveTxFragmentPageAdapter; 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.AndroidUtils; 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; @@ -356,27 +357,40 @@ private ParsedTransaction fillAssetDependentControls(View view, NetworkInfo sele getResources().getString(R.string.activate_erc20), parsedTx.getSymbol())); } else if (parsedTx.getIsSwap()) { txType.setText(getResources().getString(R.string.swap)); + } else if (parsedTx.isSolanaDappTransaction) { + txType.setText(R.string.brave_wallet_approve_transaction); } else { txType.setText(getResources().getString(R.string.send)); } - String amountText = - String.format(getResources().getString(R.string.crypto_wallet_amount_asset), - 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.getSender(), parsedTx.getRecipient())); - TextView amountAsset = view.findViewById(R.id.amount_asset); TextView amountFiat = view.findViewById(R.id.amount_fiat); - amountFiat.setText( - String.format(getResources().getString(R.string.crypto_wallet_amount_fiat), - String.format(Locale.getDefault(), "%.2f", parsedTx.getFiatTotal()))); - if (mTxInfo.txType == TransactionType.ERC721_TRANSFER_FROM - || mTxInfo.txType == TransactionType.ERC721_SAFE_TRANSFER_FROM) { - amountText = Utils.tokenToString(parsedTx.getErc721BlockchainToken()); - amountFiat.setVisibility(View.GONE); // Display NFT values in the future + TextView amountAsset = view.findViewById(R.id.amount_asset); + if (parsedTx.isSolanaDappTransaction) { + AndroidUtils.gone(amountFiat, amountAsset); + } else { + amountFiat.setText( + String.format(getResources().getString(R.string.crypto_wallet_amount_fiat), + String.format(Locale.getDefault(), "%.2f", parsedTx.getFiatTotal()))); + String amountText = + String.format(getResources().getString(R.string.crypto_wallet_amount_asset), + parsedTx.formatValueToDisplay(), parsedTx.getSymbol()); + + if (mTxInfo.txType == TransactionType.ERC721_TRANSFER_FROM + || mTxInfo.txType == TransactionType.ERC721_SAFE_TRANSFER_FROM) { + amountText = Utils.tokenToString(parsedTx.getErc721BlockchainToken()); + amountFiat.setVisibility(View.GONE); // Display NFT values in the future + } + amountAsset.setText(amountText); + } + + TextView fromTo = view.findViewById(R.id.from_to); + if (parsedTx.getSender() != null && !parsedTx.getSender().equals(parsedTx.getRecipient())) { + fromTo.setText(String.format(getResources().getString(R.string.crypto_wallet_from_to), + mAccountName, parsedTx.getSender(), "->", parsedTx.getRecipient())); + } else { + fromTo.setText(String.format(getResources().getString(R.string.crypto_wallet_from_to), + mAccountName, parsedTx.getSender(), "", "")); } - amountAsset.setText(amountText); setupPager(view, selectedNetwork, accounts, assetPrices, fullTokenList, nativeAssetsBalances, blockchainTokensBalances); return parsedTx; diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/SolanaTxDetailsFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/SolanaTxDetailsFragment.java new file mode 100644 index 0000000000000..821466f01f1df --- /dev/null +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/SolanaTxDetailsFragment.java @@ -0,0 +1,39 @@ +/* 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.fragments; + +import android.content.Context; + +import androidx.fragment.app.Fragment; + +import org.chromium.brave_wallet.mojom.SolanaInstruction; +import org.chromium.brave_wallet.mojom.TransactionInfo; +import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter; +import org.chromium.chrome.browser.crypto_wallet.presenters.SolanaInstructionPresenter; +import org.chromium.chrome.browser.crypto_wallet.util.TransactionUtils; + +import java.util.ArrayList; +import java.util.List; + +public class SolanaTxDetailsFragment { + public static Fragment newInstance(TransactionInfo txInfo, Context context) { + List details = new ArrayList<>(); + SolanaInstruction[] instructions = + TransactionUtils.safeSolData(txInfo.txDataUnion).instructions; + details.add(new TwoLineItemRecyclerViewAdapter.TwoLineItemText( + context.getString(TransactionUtils.getTxType(txInfo)))); + + for (SolanaInstruction solanaInstruction : instructions) { + SolanaInstructionPresenter solanaInstructionPresenter = + new SolanaInstructionPresenter(solanaInstruction); + details.addAll(solanaInstructionPresenter.toTwoLineList(context)); + if (instructions.length > 1) { + details.add(new TwoLineItemRecyclerViewAdapter.TwoLineItemDivider()); + } + } + return new TwoLineItemFragment(details); + } +} diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TwoLineItemFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TwoLineItemFragment.java index 9340353a138de..a306c7a3c7742 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TwoLineItemFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TwoLineItemFragment.java @@ -8,6 +8,7 @@ import android.annotation.SuppressLint; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -37,6 +38,7 @@ public static TwoLineItemFragment newInstance(List items) { return fragment; } + @SuppressLint("ClickableViewAccessibility") @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -44,6 +46,24 @@ public View onCreateView( RecyclerView recyclerView = (RecyclerView) view; adapter = new TwoLineItemRecyclerViewAdapter(items); recyclerView.setAdapter(adapter); + recyclerView.setOnTouchListener((v, event) -> { + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + // Disallow NestedScrollView to intercept touch events. + v.getParent().requestDisallowInterceptTouchEvent(true); + break; + + case MotionEvent.ACTION_UP: + // Allow NestedScrollView to intercept touch events. + v.getParent().requestDisallowInterceptTouchEvent(false); + break; + } + + // Handle RecyclerView touch events. + v.onTouchEvent(event); + return true; + }); return view; } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxDetailsFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxDetailsFragment.java index 95273e18f6965..c409475a9b6e2 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxDetailsFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxDetailsFragment.java @@ -24,6 +24,7 @@ import org.chromium.brave_wallet.mojom.TxData1559; import org.chromium.brave_wallet.mojom.TxDataUnion; import org.chromium.chrome.R; +import org.chromium.chrome.browser.crypto_wallet.util.TransactionUtils; import org.chromium.chrome.browser.crypto_wallet.util.Utils; public class TxDetailsFragment extends Fragment { @@ -59,35 +60,7 @@ public void setupView(View view) { return; } assert mTxInfo.txParams.length == mTxInfo.txArgs.length; - String functionType = getResources().getString(R.string.wallet_details_function_type_other); - switch (mTxInfo.txType) { - case TransactionType.ERC20_TRANSFER: - functionType = getResources().getString( - R.string.wallet_details_function_type_erc20transfer); - break; - case TransactionType.ERC20_APPROVE: - functionType = getResources().getString( - R.string.wallet_details_function_type_erc20approve); - break; - case TransactionType.ERC721_TRANSFER_FROM: - functionType = getResources().getString( - R.string.wallet_details_function_type_erc721transfer); - break; - case TransactionType.SOLANA_SYSTEM_TRANSFER: - functionType = getResources().getString( - R.string.wallet_details_function_type_solana_system_transfer); - break; - case TransactionType.SOLANA_SPL_TOKEN_TRANSFER: - functionType = getResources().getString( - R.string.wallet_details_function_type_spl_token_transfer); - break; - case TransactionType.SOLANA_SPL_TOKEN_TRANSFER_WITH_ASSOCIATED_TOKEN_ACCOUNT_CREATION: - functionType = getResources().getString( - R.string.wallet_details_function_type_solana_spl_token_transfer_with_associated_token_account_creation); - break; - default: - break; - } + String functionType = getString(TransactionUtils.getTxType(mTxInfo)); TextView functionTypeWidget = view.findViewById(R.id.function_type); functionTypeWidget.setText(String.format( diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxFragment.java index d4500b925edc4..071167be6c989 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/TxFragment.java @@ -25,20 +25,21 @@ import androidx.fragment.app.Fragment; import org.chromium.brave_wallet.mojom.AccountInfo; -import org.chromium.brave_wallet.mojom.AssetPriceTimeframe; import org.chromium.brave_wallet.mojom.BlockchainToken; import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.EthTxManagerProxy; import org.chromium.brave_wallet.mojom.NetworkInfo; +import org.chromium.brave_wallet.mojom.SolanaSendTransactionOptions; +import org.chromium.brave_wallet.mojom.SolanaTxData; import org.chromium.brave_wallet.mojom.TransactionInfo; -import org.chromium.brave_wallet.mojom.TransactionType; import org.chromium.chrome.R; import org.chromium.chrome.browser.crypto_wallet.activities.AdvanceTxSettingActivity; -import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletActivity; import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletBaseActivity; -import org.chromium.chrome.browser.crypto_wallet.activities.BuySendSwapActivity; +import org.chromium.chrome.browser.crypto_wallet.presenters.Amount; +import org.chromium.chrome.browser.crypto_wallet.util.AndroidUtils; import org.chromium.chrome.browser.crypto_wallet.util.ParsedTransaction; import org.chromium.chrome.browser.crypto_wallet.util.ParsedTransactionFees; +import org.chromium.chrome.browser.crypto_wallet.util.TransactionUtils; import org.chromium.chrome.browser.crypto_wallet.util.Utils; import org.chromium.chrome.browser.crypto_wallet.util.WalletConstants; @@ -64,6 +65,7 @@ public class TxFragment extends Fragment { // don't use them. It would be good to eventually migrate to observers everywhere. private boolean mUpdateTxObjectManually; public static final int START_ADVANCE_SETTING_ACTIVITY_CODE = 0; + private boolean mIsSolanaInstruction; public static TxFragment newInstance(TransactionInfo txInfo, NetworkInfo selectedNetwork, AccountInfo[] accounts, HashMap assetPrices, @@ -99,6 +101,7 @@ private TxFragment(TransactionInfo txInfo, NetworkInfo selectedNetwork, AccountI mPreviousCheckedPriorityId = -1; mUpdateTxObjectManually = updateTxObjectManually; mSolanaEstimatedTxFee = solanaEstimatedTxFee; + mIsSolanaInstruction = TransactionUtils.isSolanaTx(txInfo); } @Override @@ -116,7 +119,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c View advanceSettingContainer = view.findViewById(R.id.fragment_tx_tv_advance_setting); advanceSettingContainer.setVisibility( - isAdvanceSettingEnabled(mSelectedNetwork) ? View.VISIBLE : View.INVISIBLE); + isAdvanceSettingEnabled(mSelectedNetwork) ? View.VISIBLE : View.GONE); advanceSettingContainer.setOnClickListener(v -> { Intent toAdvanceTxSetting = @@ -407,27 +410,66 @@ private void setupView(View view) { mAssetPrices, mSolanaEstimatedTxFee, mFullTokenList, mNativeAssetsBalances, mBlockchainTokensBalances); + if (mIsSolanaInstruction) { + TextView gasTxTv = view.findViewById(R.id.frag_tx_tv_gas); + TextView totalHeading = view.findViewById(R.id.frag_tx_tv_total_heading); + + gasTxTv.setText(R.string.brave_wallet_allow_spend_transaction_fee); + totalHeading.setText(R.string.brave_wallet_confirm_transaction_amount_fee); + SolanaTxData solanaTxData = TransactionUtils.safeSolData(mTxInfo.txDataUnion); + if (solanaTxData != null && solanaTxData.sendOptions != null) { + SolanaSendTransactionOptions sendTxOptions = solanaTxData.sendOptions; + LinearLayout sendOptionsLinearLayout = + view.findViewById(R.id.frag_tx_ll_send_options); + if (sendTxOptions.maxRetries != null) { + TextView tvLabel = AndroidUtils.makeHeaderTv(requireContext()); + TextView tvVal = AndroidUtils.makeSubHeaderTv(requireContext()); + tvLabel.setText(R.string.brave_wallet_solana_max_retries); + tvVal.setText(String.valueOf(sendTxOptions.maxRetries.maxRetries)); + sendOptionsLinearLayout.addView(tvLabel); + sendOptionsLinearLayout.addView(tvVal); + } + if (sendTxOptions.preflightCommitment != null) { + TextView tvLabel = AndroidUtils.makeHeaderTv(requireContext()); + TextView tvVal = AndroidUtils.makeSubHeaderTv(requireContext()); + tvLabel.setText(R.string.brave_wallet_solana_preflight_commitment); + tvVal.setText(sendTxOptions.preflightCommitment); + sendOptionsLinearLayout.addView(tvLabel); + sendOptionsLinearLayout.addView(tvVal); + } + if (sendTxOptions.skipPreflight != null) { + TextView tvLabel = AndroidUtils.makeHeaderTv(requireContext()); + TextView tvVal = AndroidUtils.makeSubHeaderTv(requireContext()); + tvLabel.setText(R.string.brave_wallet_solana_skip_preflight); + tvVal.setText(String.valueOf(sendTxOptions.skipPreflight.skipPreflight)); + sendOptionsLinearLayout.addView(tvLabel); + sendOptionsLinearLayout.addView(tvVal); + } + } + } TextView gasFeeAmount = view.findViewById(R.id.gas_fee_amount); final double totalGas = mParsedTx.getGasFee(); - gasFeeAmount.setText(String.format( - getResources().getString(R.string.crypto_wallet_gas_fee_amount), - String.format(Locale.getDefault(), "%.8f", totalGas), mSelectedNetwork.symbol)); + String symbol = + mParsedTx.getSymbol() == null ? mParsedTx.getSymbol() : mSelectedNetwork.symbol; + gasFeeAmount.setText( + String.format(getResources().getString(R.string.crypto_wallet_gas_fee_amount), + String.format(Locale.getDefault(), "%.8f", totalGas), symbol)); String valueAssetText = mParsedTx.formatValueToDisplay(); TextView totalAmount = view.findViewById(R.id.total_amount); - totalAmount.setText(String.format( - getResources().getString(R.string.crypto_wallet_total_amount), valueAssetText, - mParsedTx.getSymbol(), String.format(Locale.getDefault(), "%.8f", totalGas), - mSelectedNetwork.symbol)); + totalAmount.setText( + String.format(getResources().getString(R.string.crypto_wallet_total_amount), + valueAssetText, mParsedTx.getSymbol(), + String.format(Locale.getDefault(), "%.8f", totalGas), symbol)); TextView gasFeeAmountFiat = view.findViewById(R.id.gas_fee_amount_fiat); gasFeeAmountFiat.setText( String.format(getResources().getString(R.string.crypto_wallet_amount_fiat), - String.format(Locale.getDefault(), "%.2f", mParsedTx.getGasFeeFiat()))); + new Amount(mParsedTx.getFiatTotal()).toStringFormat())); TextView totalAmountFiat = view.findViewById(R.id.total_amount_fiat); totalAmountFiat.setText( String.format(getResources().getString(R.string.crypto_wallet_amount_fiat), - String.format(Locale.getDefault(), "%.2f", mParsedTx.getFiatTotal()))); + new Amount(mParsedTx.getFiatTotal()).toStringFormat())); } @Override diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/SignTransactionFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/SignTransactionFragment.java index d9911cfc3f55e..b820f63199e8b 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/SignTransactionFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/SignTransactionFragment.java @@ -38,7 +38,7 @@ import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter; import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItem; import org.chromium.chrome.browser.crypto_wallet.fragments.TwoLineItemFragment; -import org.chromium.chrome.browser.crypto_wallet.model.SolanaInstructionPresenter; +import org.chromium.chrome.browser.crypto_wallet.presenters.SolanaInstructionPresenter; import org.chromium.chrome.browser.crypto_wallet.util.NavigationItem; import org.chromium.chrome.browser.crypto_wallet.util.TransactionUtils; import org.chromium.chrome.browser.crypto_wallet.util.Utils; diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/modal/BraveWalletPanel.java b/android/java/org/chromium/chrome/browser/crypto_wallet/modal/BraveWalletPanel.java index ae8c676d3b5fb..810da66f4baff 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/modal/BraveWalletPanel.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/modal/BraveWalletPanel.java @@ -87,7 +87,7 @@ public class BraveWalletPanel implements DialogInterface { private WalletModel mWalletModel; private AccountInfo mSelectedAccount; private NetworkInfo mSelectedNetwork; - private Observer mAccountInfoObserver = accountInfo -> { + private final Observer mAccountInfoObserver = accountInfo -> { if (accountInfo == null) return; mSelectedAccount = accountInfo; mBraveWalletPanelServices.getKeyringService().getKeyringInfo( diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/presenters/Amount.java b/android/java/org/chromium/chrome/browser/crypto_wallet/presenters/Amount.java new file mode 100644 index 0000000000000..8a6280c2a68a0 --- /dev/null +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/presenters/Amount.java @@ -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.presenters; + +import android.text.TextUtils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +public class Amount { + private BigDecimal mValue; + + public Amount(BigDecimal value) { + if (value == null) { + this.mValue = BigDecimal.ZERO; + return; + } + this.mValue = value; + } + + public Amount(String value) { + if (TextUtils.isEmpty(value)) { + this.mValue = BigDecimal.ZERO; + return; + } + this.mValue = new BigDecimal(value); + } + + public Amount(double value) { + this.mValue = new BigDecimal(value); + } + + public Amount add(BigDecimal value) { + BigDecimal safeVal = safeVal(value); + mValue = mValue.add(safeVal); + return this; + } + + public Amount add(Amount value) { + BigDecimal safeVal = safeVal(value); + mValue = mValue.add(safeVal); + return this; + } + + // Default max decimal precision is 8 + public String toStringFormat() { + return toStringFormat(8); + } + + public String toStringFormat(int decimalLength) { + return mValue.setScale(decimalLength, RoundingMode.HALF_UP) + .stripTrailingZeros() + .toPlainString(); + } + + public String toHex() { + if (mValue.signum() == 0) return "0x0"; + return "0x" + mValue.toBigInteger().toString(16); + } + + private static BigDecimal safeVal(BigDecimal value) { + return (value == null) ? BigDecimal.ZERO : value; + } + + private static BigDecimal safeVal(Amount value) { + return (value != null && value.mValue != null) ? value.mValue : BigDecimal.ZERO; + } + + private void safeInit() { + if (mValue == null) mValue = BigDecimal.ZERO; + } + + public BigDecimal getValue() { + return BigDecimal.valueOf(mValue.doubleValue()); + } +} diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/model/SolanaInstructionPresenter.java b/android/java/org/chromium/chrome/browser/crypto_wallet/presenters/SolanaInstructionPresenter.java similarity index 70% rename from android/java/org/chromium/chrome/browser/crypto_wallet/model/SolanaInstructionPresenter.java rename to android/java/org/chromium/chrome/browser/crypto_wallet/presenters/SolanaInstructionPresenter.java index fb5e9a74b04d4..cf0ac7cbaeec1 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/model/SolanaInstructionPresenter.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/presenters/SolanaInstructionPresenter.java @@ -1,6 +1,12 @@ -package org.chromium.chrome.browser.crypto_wallet.model; +/* 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.presenters; import android.content.Context; +import android.text.TextUtils; import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.SolanaAccountMeta; @@ -9,7 +15,6 @@ import org.chromium.brave_wallet.mojom.SolanaInstructionParam; import org.chromium.chrome.R; import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItem; -import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItemDivider; import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItemHeader; import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItemText; import org.chromium.chrome.browser.crypto_wallet.util.TransactionUtils; @@ -26,6 +31,9 @@ public class SolanaInstructionPresenter { public boolean mIsUnknown; private boolean isDecodedDataPresent; private SolanaInstruction mSolanaInstruction; + private Integer mInstructionType; + private String mFromPubKey; + private String mToPubKey; public SolanaInstructionPresenter(SolanaInstruction solanaInstruction) { assert solanaInstruction != null : "solanaInstruction is null"; @@ -74,7 +82,7 @@ public List toTwoLineList(Context context) { TransactionUtils.getSolanaProgramIdName(mSolanaInstruction.programId, context) + " - " + context.getString( - TransactionUtils.getSolType(mSolanaInstruction.programId, + TransactionUtils.getSolTxSubType(mSolanaInstruction.programId, mSolanaInstruction.decodedData != null ? mSolanaInstruction.decodedData.instructionType : -1)), @@ -87,10 +95,14 @@ public List toTwoLineList(Context context) { twoLineItems.addAll(solanaInstructionPresenter.accountDataToList()); if (shouldShowRawData()) { - // add data field also + // Add data and program id field also twoLineItems.add( new TwoLineItemHeader(context.getString(R.string.brave_wallet_data_text))); twoLineItems.addAll(solanaInstructionPresenter.dataToList()); + + twoLineItems.add( + new TwoLineItemHeader(context.getString(R.string.brave_wallet_tx_progam_id))); + twoLineItems.addAll(solanaInstructionPresenter.programIdToList()); } twoLineItems.addAll(solanaInstructionPresenter.accountParamDataToList()); return twoLineItems; @@ -128,18 +140,84 @@ public List accountParamDataToList() { return twoLineItemDataSources; } + public List programIdToList() { + return Arrays.asList(new TwoLineItemText(null, mSolanaInstruction.programId)); + } + public List dataToList() { return Arrays.asList(new TwoLineItemText(null, Arrays.toString(mSolanaInstruction.data))); } + // Get lamport from decoded data params + public String getLamportAmount() { + if (isDecodedDataPresent && mSolanaInstruction.decodedData.params != null) { + for (SolanaInstructionParam instructionParam : mSolanaInstruction.decodedData.params) { + if (instructionParam.name.equalsIgnoreCase(WalletConstants.SOL_LAMPORTS)) { + return instructionParam.value; + } + } + } + return "0"; + } + + public Integer getInstructionType() { + if (mInstructionType != null) return mInstructionType; + if (isDecodedDataPresent) { + mInstructionType = mSolanaInstruction.decodedData.instructionType; + return mInstructionType; + } + return null; + } + + public String fromPubKey() { + if (mFromPubKey != null) return mFromPubKey; + mFromPubKey = getPubKeyPerParamKey(WalletConstants.SOL_DAPP_FROM_ACCOUNT); + return mFromPubKey; + } + + public String toPubKey() { + if (mToPubKey != null) return mToPubKey; + mToPubKey = getPubKeyPerParamKey(WalletConstants.SOL_DAPP_TO_ACCOUNT); + return mToPubKey; + } + + // Returns the first found account pub key from accounts meta, corresponding to input "key" from + // accountParams + public String getPubKeyPerParamKey(String key) { + if (TextUtils.isEmpty(key)) return null; + String pubKey = null; + if (isAccountMetaPresent()) { + SolanaInstructionAccountParam[] accountParams = + mSolanaInstruction.decodedData.accountParams; + for (int i = 0; i < accountParams.length; i++) { + if (accountParams[i].name.equalsIgnoreCase(key)) { + if (mSolanaInstruction.accountMetas.length > i) { + pubKey = mSolanaInstruction.accountMetas[i].pubkey; + } + return pubKey; + } + } + } + return pubKey; + } + public SolanaInstruction getSolanaInstruction() { return mSolanaInstruction; } + public boolean isTokenInstruction() { + return mSolanaInstruction.programId.equals(WalletConstants.SOL_INS_TOKEN); + } + public boolean shouldShowRawData() { return mIsUnknown || mSolanaInstruction.decodedData == null; } + private boolean isAccountMetaPresent() { + return isDecodedDataPresent && mSolanaInstruction.decodedData.accountParams != null + && mSolanaInstruction.accountMetas != null; + } + public static class SolanaInstructionAccountPresenter { public String mPubKey; public String mLocalizeAccountHeader; diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java index add666f4ab533..295270aa4403b 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/AndroidUtils.java @@ -1,14 +1,18 @@ package org.chromium.chrome.browser.crypto_wallet.util; import android.content.Context; +import android.graphics.Typeface; import android.os.Build; import android.text.Html; import android.text.Spanned; import android.util.TypedValue; import android.view.View; +import android.widget.TextView; import androidx.annotation.IdRes; +import org.chromium.chrome.R; + public class AndroidUtils { public static int getToolBarHeight(Context context) { TypedValue tv = new TypedValue(); @@ -43,4 +47,35 @@ public static Spanned formatHTML(String html) { return Html.fromHtml(html); } } + + // Views + public static TextView makeHeaderTv(Context context) { + TextView textView = new TextView(context); + textView.setTextAppearance(R.style.BraveWalletTextViewTitle); + textView.setTypeface(null, Typeface.BOLD); + textView.setId(View.generateViewId()); + return textView; + } + + public static TextView makeSubHeaderTv(Context context) { + TextView textView = new TextView(context); + textView.setTextAppearance(R.style.BraveWalletTextViewSubTitle); + textView.setId(View.generateViewId()); + return textView; + } + + public static void gone(View... views) { + setViewVisibility(false, views); + } + + public static void show(View... views) { + setViewVisibility(true, views); + } + + private static void setViewVisibility(boolean isVisible, View... views) { + int visibility = isVisible ? View.VISIBLE : View.GONE; + for (View view : views) { + view.setVisibility(visibility); + } + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/ParsedTransaction.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/ParsedTransaction.java index 35184c92c488e..6f59a68161863 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/ParsedTransaction.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/ParsedTransaction.java @@ -5,26 +5,30 @@ package org.chromium.chrome.browser.crypto_wallet.util; +import static org.chromium.chrome.browser.crypto_wallet.util.WalletConstants.SOLANA_TRANSACTION_TYPES; + import android.content.Context; +import android.text.TextUtils; +import android.util.Log; import android.util.Pair; -import org.chromium.base.Log; import org.chromium.brave_wallet.mojom.AccountInfo; import org.chromium.brave_wallet.mojom.BlockchainToken; -import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.FilTxData; import org.chromium.brave_wallet.mojom.NetworkInfo; +import org.chromium.brave_wallet.mojom.SolanaInstruction; +import org.chromium.brave_wallet.mojom.SolanaSystemInstruction; +import org.chromium.brave_wallet.mojom.SolanaTokenInstruction; import org.chromium.brave_wallet.mojom.SolanaTxData; import org.chromium.brave_wallet.mojom.TransactionInfo; -import org.chromium.brave_wallet.mojom.TransactionStatus; import org.chromium.brave_wallet.mojom.TransactionType; -import org.chromium.brave_wallet.mojom.TxData; import org.chromium.brave_wallet.mojom.TxData1559; import org.chromium.brave_wallet.mojom.TxDataUnion; import org.chromium.chrome.R; -import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletBaseActivity; +import org.chromium.chrome.browser.crypto_wallet.presenters.SolanaInstructionPresenter; import org.chromium.mojo_base.mojom.TimeDelta; +import java.math.BigDecimal; import java.math.BigInteger; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -33,6 +37,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -78,6 +83,9 @@ public class ParsedTransaction extends ParsedTransactionFees { private BlockchainToken buyToken; private double minBuyAmount; + // Solana + public boolean isSolanaDappTransaction; + // There are too many fields to init here private ParsedTransaction(ParsedTransactionFees parsedTransactionFees) { super(parsedTransactionFees.getGasLimit(), parsedTransactionFees.getGasPrice(), @@ -89,126 +97,6 @@ private ParsedTransaction(ParsedTransactionFees parsedTransactionFees) { parsedTransactionFees.getGasPremium(), parsedTransactionFees.getGasFeeCap()); } - public String getHash() { - return this.hash; - } - - public String getNonce() { - return this.nonce; - } - - public TimeDelta getCreatedTime() { - return this.createdTime; - } - - public int getStatus() { - return this.status; - } - - public int getType() { - return this.type; - } - - public String getSender() { - return this.sender; - } - - public String getSenderLabel() { - return this.senderLabel; - } - - public String getRecipient() { - return this.recipient; - } - - public String getRecipientLabel() { - return this.recipientLabel; - } - - public double getFiatValue() { - return this.fiatValue; - } - - public double getFiatTotal() { - return this.fiatTotal; - } - - public double getNativeCurrencyTotal() { - return this.nativeCurrencyTotal; - } - - public double getValue() { - return this.value; - } - - public String getSymbol() { - return this.symbol; - } - - public BlockchainToken getToken() { - return this.token; - } - - public int getDecimals() { - return this.decimals; - } - - public boolean getInsufficientFundsForGasError() { - return this.insufficientFundsForGasError; - } - - public boolean getInsufficientFundsError() { - return this.insufficientFundsError; - } - - public String getContractAddressError() { - return this.contractAddressError; - } - - public String getSameAddressError() { - return this.sameAddressError; - } - - public BlockchainToken getErc721BlockchainToken() { - return this.erc721BlockchainToken; - } - - public String getErc721TokenId() { - return this.erc721TokenId; - } - - public boolean getIsSwap() { - return this.isSwap; - } - - public String getApprovalTarget() { - return this.approvalTarget; - } - - public String getApprovalTargetLabel() { - return this.approvalTargetLabel; - } - - public boolean getIsApprovalUnlimited() { - return this.isApprovalUnlimited; - } - - public BlockchainToken getSellToken() { - return this.sellToken; - } - - public double getSellAmount() { - return this.sellAmount; - } - - public BlockchainToken getBuyToken() { - return this.buyToken; - } - - public double getMinBuyAmount() { - return this.minBuyAmount; - } - private static BlockchainToken findToken( BlockchainToken[] fullTokenList, String contractAddress) { if (contractAddress == null) return null; @@ -252,25 +140,22 @@ public static ParsedTransaction parseTransaction(TransactionInfo txInfo, final ParsedTransactionFees feeDetails = ParsedTransactionFees.parseTransactionFees( txInfo, selectedNetwork, networkSpotPrice, solFeeEstimatesFee); TxDataUnion txDataUnion = txInfo.txDataUnion; - TxData1559 txData = txInfo.txDataUnion.which() == TxDataUnion.Tag.EthTxData1559 - ? txInfo.txDataUnion.getEthTxData1559() + TxData1559 txData = txDataUnion.which() == TxDataUnion.Tag.EthTxData1559 + ? txDataUnion.getEthTxData1559() : null; - SolanaTxData solTxData = txInfo.txDataUnion.which() == TxDataUnion.Tag.SolanaTxData - ? txInfo.txDataUnion.getSolanaTxData() + SolanaTxData solTxData = txDataUnion.which() == TxDataUnion.Tag.SolanaTxData + ? txDataUnion.getSolanaTxData() : null; - ; - FilTxData filTxData = txInfo.txDataUnion.which() == TxDataUnion.Tag.FilTxData - ? txInfo.txDataUnion.getFilTxData() + FilTxData filTxData = txDataUnion.which() == TxDataUnion.Tag.FilTxData + ? txDataUnion.getFilTxData() : null; - ; final boolean isFilTransaction = filTxData != null; final boolean isSPLTransaction = txInfo.txType == TransactionType.SOLANA_SPL_TOKEN_TRANSFER || txInfo.txType == TransactionType .SOLANA_SPL_TOKEN_TRANSFER_WITH_ASSOCIATED_TOKEN_ACCOUNT_CREATION; - final boolean isSolTransaction = - txInfo.txType == TransactionType.SOLANA_SYSTEM_TRANSFER || isSPLTransaction; + final boolean isSolTransaction = SOLANA_TRANSACTION_TYPES.contains(txInfo.txType); final String value = isSPLTransaction ? solTxData != null ? String.valueOf(solTxData.amount) : "" @@ -279,9 +164,9 @@ public static ParsedTransaction parseTransaction(TransactionInfo txInfo, : txData != null ? txData.baseData.value : ""; - final String to = isSolTransaction ? solTxData != null ? solTxData.toWalletAddress : "" - : isFilTransaction ? filTxData.to - : txData.baseData.to; + String to = isSolTransaction ? solTxData != null ? solTxData.toWalletAddress : "" + : isFilTransaction ? filTxData.to + : txData.baseData.to; final String nonce = txData != null ? txData.baseData.nonce : ""; AccountInfo account = Utils.findAccount(accounts, txInfo.fromAddress); @@ -310,8 +195,116 @@ public static ParsedTransaction parseTransaction(TransactionInfo txInfo, parsedTransaction.status = txInfo.txStatus; parsedTransaction.sender = txInfo.fromAddress; parsedTransaction.senderLabel = getAddressLabel(accounts, txInfo.fromAddress); + parsedTransaction.isSolanaDappTransaction = + WalletConstants.SOLANA_DAPPS_TRANSACTION_TYPES.contains(txInfo.txType); + + int txType = txInfo.txType; + if (txType == TransactionType.SOLANA_DAPP_SIGN_TRANSACTION + || txType == TransactionType.SOLANA_DAPP_SIGN_AND_SEND_TRANSACTION + || txType == TransactionType.SOLANA_SWAP + || txType == TransactionType.OTHER && solTxData != null) { + if (solTxData == null) { + parsedTransaction.recipient = ""; + parsedTransaction.recipientLabel = ""; + parsedTransaction.fiatTotal = 0; + parsedTransaction.fiatValue = 0; + parsedTransaction.decimals = 0; + parsedTransaction.symbol = ""; + return parsedTransaction; + } + BigDecimal lamportTransferredAmount = new BigDecimal(value); + for (SolanaInstruction solanaInstruction : solTxData.instructions) { + SolanaInstructionPresenter presenter = + new SolanaInstructionPresenter(solanaInstruction); + String lamport = presenter.getLamportAmount(); + Integer instructionType = presenter.getInstructionType(); + boolean isInsExists = instructionType != null; + if (isInsExists + && (instructionType == SolanaSystemInstruction.TRANSFER + || instructionType == SolanaSystemInstruction.TRANSFER_WITH_SEED + || (presenter.isTokenInstruction() + && instructionType == SolanaTokenInstruction.TRANSFER))) { + String fromPubKey = presenter.fromPubKey(); + String toPubKey = presenter.toPubKey(); + if (TextUtils.isEmpty(to)) { + to = toPubKey; + } + + // only show lamports as transferred if the amount is going to a different + // pubKey + if (!toPubKey.equals(fromPubKey)) { + lamportTransferredAmount = + lamportTransferredAmount.add(new BigDecimal(lamport)); + } + } else if (isInsExists + && (instructionType == SolanaSystemInstruction.WITHDRAW_NONCE_ACCOUNT)) { + String noncePubKey = + presenter.getPubKeyPerParamKey(WalletConstants.SOL_DAPP_NONCE_ACCOUNT); + String toPubKey = presenter.toPubKey(); + if (TextUtils.isEmpty(to)) { + to = toPubKey; + } + if (noncePubKey != null && noncePubKey.equals(txInfo.fromAddress)) { + lamportTransferredAmount = + lamportTransferredAmount.add(new BigDecimal(lamport)); + } else if (!TextUtils.isEmpty(toPubKey) + && toPubKey.equals(txInfo.fromAddress)) { + lamportTransferredAmount = + lamportTransferredAmount.subtract(new BigDecimal(lamport)); + } + } else if (isInsExists + && (instructionType == SolanaSystemInstruction.CREATE_ACCOUNT + || instructionType + == SolanaSystemInstruction.CREATE_ACCOUNT_WITH_SEED)) { + String fromPubKey = presenter.fromPubKey(); + String newAccountPubKey = + presenter.getPubKeyPerParamKey(WalletConstants.SOL_DAPP_NEW_ACCOUNT); + if (TextUtils.isEmpty(to)) { + to = newAccountPubKey; + } + if (!TextUtils.isEmpty(fromPubKey) && fromPubKey.equals(txInfo.fromAddress)) { + lamportTransferredAmount = + lamportTransferredAmount.add(new BigDecimal(lamport)); + } + } else { + if (presenter.mIsUnknown) { + if (TextUtils.isEmpty(to)) { + try { + to = solanaInstruction.accountMetas[0].pubkey; + } catch (Exception ignored) { + } + } + } else { + lamportTransferredAmount = + lamportTransferredAmount.add(new BigDecimal(lamport)); + } + } + } + final int decimals = token != null ? token.decimals : Utils.SOL_DEFAULT_DECIMALS; + final double price = Utils.getOrDefault(assetPrices, tokenSymbolLower, 0.0d); + final double sendAmount = Utils.getBalanceForCoinType( + TransactionUtils.getCoinFromTxDataUnion(txDataUnion), decimals, + lamportTransferredAmount.toPlainString()); + final double sendAmountFiat = sendAmount * price; + final double totalAmountFiat = parsedTransaction.getGasFeeFiat() + sendAmountFiat; + final boolean insufficientNativeFunds = + parsedTransaction.getGasFee() > accountNativeBalance; + final boolean insufficientTokenFunds = sendAmount > accountTokenBalance; - if (txInfo.txType == TransactionType.ERC20_TRANSFER && txInfo.txArgs.length > 1) { + parsedTransaction.recipient = to; + parsedTransaction.recipientLabel = getAddressLabel(accounts, to); + parsedTransaction.fiatValue = sendAmountFiat; + parsedTransaction.fiatTotal = totalAmountFiat; + parsedTransaction.nativeCurrencyTotal = sendAmountFiat / networkSpotPrice; + parsedTransaction.value = sendAmount; + parsedTransaction.symbol = token != null ? token.symbol : ""; + parsedTransaction.decimals = Utils.SOL_DEFAULT_DECIMALS; + parsedTransaction.insufficientFundsError = insufficientTokenFunds; + parsedTransaction.insufficientFundsForGasError = insufficientNativeFunds; + parsedTransaction.isSwap = txType == TransactionType.SOLANA_SWAP; + parsedTransaction.contractAddressError = checkForContractAddressError(fullTokenList, + solTxData.toWalletAddress != null ? solTxData.toWalletAddress : to); + } else if (txInfo.txType == TransactionType.ERC20_TRANSFER && txInfo.txArgs.length > 1) { final String address = txInfo.txArgs[0]; final String amount = txInfo.txArgs[1]; final int decimals = token != null ? token.decimals : Utils.ETH_DEFAULT_DECIMALS; @@ -531,7 +524,9 @@ public String formatValueToDisplay() { } else if (this.isSwap) { return String.format(Locale.getDefault(), "%.4f", this.value); } else { - return String.format(Locale.getDefault(), "%.4f", this.value); + String sVal = String.format(Locale.getDefault(), "%.9f", value); + // Show amount without trailing zeros + return !sVal.contains(".") ? sVal : sVal.replaceAll("0*$", "").replaceAll("\\.$", ""); } } @@ -575,4 +570,124 @@ public Pair makeTxListItemTitles(Context context) { return new Pair(action, detailInfo); } + + public String getHash() { + return this.hash; + } + + public String getNonce() { + return this.nonce; + } + + public TimeDelta getCreatedTime() { + return this.createdTime; + } + + public int getStatus() { + return this.status; + } + + public int getType() { + return this.type; + } + + public String getSender() { + return this.sender; + } + + public String getSenderLabel() { + return this.senderLabel; + } + + public String getRecipient() { + return this.recipient; + } + + public String getRecipientLabel() { + return this.recipientLabel; + } + + public double getFiatValue() { + return this.fiatValue; + } + + public double getFiatTotal() { + return this.fiatTotal; + } + + public double getNativeCurrencyTotal() { + return this.nativeCurrencyTotal; + } + + public double getValue() { + return this.value; + } + + public String getSymbol() { + return this.symbol; + } + + public BlockchainToken getToken() { + return this.token; + } + + public int getDecimals() { + return this.decimals; + } + + public boolean getInsufficientFundsForGasError() { + return this.insufficientFundsForGasError; + } + + public boolean getInsufficientFundsError() { + return this.insufficientFundsError; + } + + public String getContractAddressError() { + return this.contractAddressError; + } + + public String getSameAddressError() { + return this.sameAddressError; + } + + public BlockchainToken getErc721BlockchainToken() { + return this.erc721BlockchainToken; + } + + public String getErc721TokenId() { + return this.erc721TokenId; + } + + public boolean getIsSwap() { + return this.isSwap; + } + + public String getApprovalTarget() { + return this.approvalTarget; + } + + public String getApprovalTargetLabel() { + return this.approvalTargetLabel; + } + + public boolean getIsApprovalUnlimited() { + return this.isApprovalUnlimited; + } + + public BlockchainToken getSellToken() { + return this.sellToken; + } + + public double getSellAmount() { + return this.sellAmount; + } + + public BlockchainToken getBuyToken() { + return this.buyToken; + } + + public double getMinBuyAmount() { + return this.minBuyAmount; + } } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/ParsedTransactionFees.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/ParsedTransactionFees.java index 441b393be4e27..e85606e284e5c 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/ParsedTransactionFees.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/ParsedTransactionFees.java @@ -5,6 +5,8 @@ package org.chromium.chrome.browser.crypto_wallet.util; +import static org.chromium.chrome.browser.crypto_wallet.util.WalletConstants.SOLANA_TRANSACTION_TYPES; + import org.chromium.base.Log; import org.chromium.brave_wallet.mojom.FilTxData; import org.chromium.brave_wallet.mojom.NetworkInfo; @@ -104,11 +106,7 @@ public static ParsedTransactionFees parseTransactionFees(TransactionInfo txInfo, : null; FilTxData filTxData = null; // TODO: add with FIL final int networkDecimals = selectedNetwork.decimals; - final boolean isSolTransaction = 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; + final boolean isSolTransaction = SOLANA_TRANSACTION_TYPES.contains(txInfo.txType); final boolean isFilTransaction = filTxData != null; final String gasLimit = isFilTransaction ? filTxData.gasLimit : (txData != null ? txData.baseData.gasLimit : ""); diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/SolanaTransactionsGasHelper.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/SolanaTransactionsGasHelper.java index a4b492a1df823..4c7828709f995 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/SolanaTransactionsGasHelper.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/SolanaTransactionsGasHelper.java @@ -59,7 +59,7 @@ public void maybeGetSolanaGasEstimations(Runnable runWhenDone) { estimatesContext.txMetaId = txInfo.id; estimatesContexts.add(estimatesContext); - if (mActivity != null) + if (mActivity.get() != null) mActivity.get().getSolanaTxManagerProxy().getEstimatedTxFee( txInfo.id, estimatesContext); } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/TransactionUtils.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/TransactionUtils.java index e9affba53f419..499fca3080d79 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/TransactionUtils.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/TransactionUtils.java @@ -16,6 +16,8 @@ import org.chromium.brave_wallet.mojom.SolanaSystemInstruction; import org.chromium.brave_wallet.mojom.SolanaTokenInstruction; import org.chromium.brave_wallet.mojom.SolanaTxData; +import org.chromium.brave_wallet.mojom.TransactionInfo; +import org.chromium.brave_wallet.mojom.TransactionType; import org.chromium.brave_wallet.mojom.TxDataUnion; import org.chromium.chrome.R; @@ -33,7 +35,39 @@ else if (txDataUnion.which() == TxDataUnion.Tag.SolanaTxData) return CoinType.ETH; } + public static @StringRes int getTxType(TransactionInfo info) { + if (info == null) return R.string.wallet_details_function_type_other; + switch (info.txType) { + case TransactionType.ERC20_TRANSFER: + return R.string.wallet_details_function_type_erc20transfer; + case TransactionType.ERC20_APPROVE: + return R.string.wallet_details_function_type_erc20approve; + case TransactionType.ERC721_TRANSFER_FROM: + return R.string.wallet_details_function_type_erc721transfer; + case TransactionType.SOLANA_SYSTEM_TRANSFER: + return R.string.wallet_details_function_type_solana_system_transfer; + case TransactionType.SOLANA_SPL_TOKEN_TRANSFER: + return R.string.wallet_details_function_type_spl_token_transfer; + case TransactionType.SOLANA_SPL_TOKEN_TRANSFER_WITH_ASSOCIATED_TOKEN_ACCOUNT_CREATION: + return R.string + .wallet_details_function_type_solana_spl_token_transfer_with_associated_token_account_creation; + case TransactionType.SOLANA_DAPP_SIGN_AND_SEND_TRANSACTION: + return R.string.wallet_details_function_type_solana_dapp_sign_and_send; + case TransactionType.SOLANA_DAPP_SIGN_TRANSACTION: + return R.string.wallet_details_function_type_solana_dapp_sign; + default: + return R.string.wallet_details_function_type_other; + } + } + // ---------- Solana ---------- + public static boolean isSolanaTx(TransactionInfo transactionInfo) { + if (transactionInfo == null || transactionInfo.txDataUnion == null) return false; + return WalletConstants.SOLANA_TRANSACTION_TYPES.contains(transactionInfo.txType) + || transactionInfo.txType == TransactionType.OTHER + && safeSolData(transactionInfo.txDataUnion) != null; + } + public static String getSolanaProgramIdName(String programId, Context context) { if (TextUtils.isEmpty(programId)) return ""; switch (programId) { @@ -58,7 +92,7 @@ public static String getSolanaProgramIdName(String programId, Context context) { } } - public static int getSolType(String programId, int instructionType) { + public static int getSolTxSubType(String programId, int instructionType) { if (TextUtils.isEmpty(programId)) return R.string.brave_wallet_unknown; switch (programId) { @@ -75,7 +109,8 @@ public static boolean isSolTxUnknown(String programId) { if (TextUtils.isEmpty(programId)) return true; switch (programId) { case WalletConstants.SOL_INS_SYSTEM: - case WalletConstants.SOL_INS_TOKEN: + case WalletConstants.SOL_INS_VOTE: + case WalletConstants.SOL_INS_STAKE: return false; default: return true; diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/util/WalletConstants.java b/android/java/org/chromium/chrome/browser/crypto_wallet/util/WalletConstants.java index 653ce7a8b29e0..5730b45c6c05a 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/util/WalletConstants.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/util/WalletConstants.java @@ -1,7 +1,7 @@ package org.chromium.chrome.browser.crypto_wallet.util; import org.chromium.brave_wallet.mojom.BraveWalletConstants; -import org.chromium.chrome.R; +import org.chromium.brave_wallet.mojom.TransactionType; import java.util.Arrays; import java.util.List; @@ -68,8 +68,22 @@ public final class WalletConstants { BraveWalletConstants.MAINNET_CHAIN_ID, BraveWalletConstants.GOERLI_CHAIN_ID); // Solana - public static final String SOL_LAMPORTS = "lamports"; public static final String SOL = "SOL"; + public static final String SOL_LAMPORTS = "lamports"; + public static final String SOL_DAPP_FROM_ACCOUNT = "from_account"; + public static final String SOL_DAPP_TO_ACCOUNT = "to_account"; + public static final String SOL_DAPP_NONCE_ACCOUNT = "nonce_account"; + public static final String SOL_DAPP_NEW_ACCOUNT = "new_account"; + + public static List SOLANA_TRANSACTION_TYPES = Arrays.asList( + TransactionType.SOLANA_SYSTEM_TRANSFER, TransactionType.SOLANA_SPL_TOKEN_TRANSFER, + TransactionType.SOLANA_SPL_TOKEN_TRANSFER_WITH_ASSOCIATED_TOKEN_ACCOUNT_CREATION, + TransactionType.SOLANA_DAPP_SIGN_TRANSACTION, + TransactionType.SOLANA_DAPP_SIGN_AND_SEND_TRANSACTION); + + public static List SOLANA_DAPPS_TRANSACTION_TYPES = + Arrays.asList(TransactionType.SOLANA_DAPP_SIGN_TRANSACTION, + TransactionType.SOLANA_DAPP_SIGN_AND_SEND_TRANSACTION); // Solana instruction types public static final String SOL_INS_SYSTEM = BraveWalletConstants.SOLANA_SYSTEM_PROGRAM_ID; diff --git a/android/java/res/layout/approve_tx_bottom_sheet.xml b/android/java/res/layout/approve_tx_bottom_sheet.xml index 3f3804e341d96..283cd82008f4e 100644 --- a/android/java/res/layout/approve_tx_bottom_sheet.xml +++ b/android/java/res/layout/approve_tx_bottom_sheet.xml @@ -159,26 +159,32 @@ app:tabIndicatorColor="@null" app:tabSelectedTextColor="@color/tab_color" app:tabTextAppearance="@style/BraveWalletTabsTextAppearance" - app:tabTextColor="@color/wallet_text_color" /> + app:tabTextColor="@color/wallet_text_color" + app:layout_constraintBottom_toTopOf="@id/navigation_view_pager"/> + + - - + app:layout_constraintTop_toBottomOf="@id/navigation_view_pager">