diff --git a/android/brave_java_resources.gni b/android/brave_java_resources.gni index 1ececb1b148..8e0353ff3ab 100644 --- a/android/brave_java_resources.gni +++ b/android/brave_java_resources.gni @@ -404,6 +404,7 @@ brave_java_resources = [ "java/res/drawable-xxxhdpi/verified_disclosure.png", "java/res/drawable-xxxhdpi/yandex.png", "java/res/drawable//ic_circle_down.xml", + "java/res/drawable//ic_warning.xml", "java/res/drawable/ac_radio_button_normal.xml", "java/res/drawable/ac_radio_button_selected.xml", "java/res/drawable/ac_radio_button_selector.xml", @@ -825,6 +826,7 @@ brave_java_resources = [ "java/res/layout/fragment_set_default_browser_bottom_sheet.xml", "java/res/layout/fragment_setup_wallet.xml", "java/res/layout/fragment_sign_message.xml", + "java/res/layout/fragment_sign_tx_message.xml", "java/res/layout/fragment_transaction.xml", "java/res/layout/fragment_transaction_details.xml", "java/res/layout/fragment_troubleshooting_onboarding.xml", @@ -835,6 +837,7 @@ brave_java_resources = [ "java/res/layout/highlight_layout.xml", "java/res/layout/icon_row_menu_footer.xml", "java/res/layout/item_create_account.xml", + "java/res/layout/item_fragment_two_line_divider_item.xml", "java/res/layout/item_fragment_two_line_item.xml", "java/res/layout/item_network_selector.xml", "java/res/layout/location_status_incognito_badge.xml", diff --git a/android/brave_java_sources.gni b/android/brave_java_sources.gni index b386c26f755..bce9f22246e 100644 --- a/android/brave_java_sources.gni +++ b/android/brave_java_sources.gni @@ -118,6 +118,7 @@ brave_java_sources = [ "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/DAppsMessageFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/EncryptionKeyFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/SignMessageFragment.java", + "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/SignTransactionFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/onboarding_fragments/BackupWalletFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/onboarding_fragments/CryptoOnboardingFragment.java", "../../brave/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/onboarding_fragments/RecoveryPhraseFragment.java", @@ -133,6 +134,7 @@ 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", diff --git a/android/java/org/chromium/chrome/browser/app/BraveActivity.java b/android/java/org/chromium/chrome/browser/app/BraveActivity.java index 4eec9fd85e3..60cb26a9377 100644 --- a/android/java/org/chromium/chrome/browser/app/BraveActivity.java +++ b/android/java/org/chromium/chrome/browser/app/BraveActivity.java @@ -399,7 +399,7 @@ public abstract class BraveActivity extends C return; } maybeShowPendingTransactions(); - maybeShowSignMessageRequestLayout(); + maybeShowSignTxRequestLayout(); }); } @@ -416,6 +416,30 @@ public abstract class BraveActivity extends C mWalletModel.getCryptoModel().refreshTransactions(); } + private void maybeShowSignTxRequestLayout() { + assert mBraveWalletService != null; + mBraveWalletService.getPendingSignTransactionRequests(requests -> { + if (requests != null && requests.length != 0) { + openBraveWalletDAppsActivity( + BraveWalletDAppsActivity.ActivityType.SIGN_TRANSACTION); + return; + } + maybeShowSignAllTxRequestLayout(); + }); + } + + private void maybeShowSignAllTxRequestLayout() { + assert mBraveWalletService != null; + mBraveWalletService.getPendingSignAllTransactionsRequests(requests -> { + if (requests != null && requests.length != 0) { + openBraveWalletDAppsActivity( + BraveWalletDAppsActivity.ActivityType.SIGN_ALL_TRANSACTIONS); + return; + } + maybeShowSignMessageRequestLayout(); + }); + } + private void maybeShowSignMessageRequestLayout() { assert mBraveWalletService != null; mBraveWalletService.getPendingSignMessageRequests(requests -> { diff --git a/android/java/org/chromium/chrome/browser/app/domain/CryptoModel.java b/android/java/org/chromium/chrome/browser/app/domain/CryptoModel.java index 6d4b5440cd8..d478fe4b8e8 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/CryptoModel.java +++ b/android/java/org/chromium/chrome/browser/app/domain/CryptoModel.java @@ -67,8 +67,7 @@ public class CryptoModel { private final MutableLiveData _mCoinTypeMutableLiveData = new MutableLiveData<>(CoinType.ETH); public final LiveData mCoinTypeMutableLiveData = _mCoinTypeMutableLiveData; - private final MutableLiveData _mProcessNextDAppsRequest = - new MutableLiveData<>(); + private final Object mLock = new Object(); private Context mContext; private SendModel mSendModel; @@ -77,8 +76,7 @@ public class CryptoModel { // Todo: create a models for portfolio // Todo: create method to create and return new models for Asset, Account, // TransactionConfirmation, SwapModel, AssetModel, SendModel - public final LiveData mProcessNextDAppsRequest = - _mProcessNextDAppsRequest; + public LiveData> mAccountInfosFromKeyRingModel; public LiveData mIsSwapEnabled; @@ -135,7 +133,7 @@ public class CryptoModel { if (mKeyringService == null || mPendingTxHelper == null) { return; } - // TODO(pav): uncomment the below and mvoe to refreshTransactions. + // TODO(pav): uncomment the below and move to refreshTransactions. // To process pending tx from all networks and full network name // mKeyringService.getKeyringsInfo(mSharedData.getEnabledKeyrings(), @@ -239,46 +237,6 @@ public class CryptoModel { } } - public void processPublicEncryptionKey(boolean isApproved, Origin origin) { - synchronized (mLock) { - if (mBraveWalletService == null) { - return; - } - mBraveWalletService.notifyGetPublicKeyRequestProcessed(isApproved, origin); - mBraveWalletService.getPendingGetEncryptionPublicKeyRequests(requests -> { - if (requests != null && requests.length > 0) { - _mProcessNextDAppsRequest.postValue(BraveWalletDAppsActivity.ActivityType - .GET_ENCRYPTION_PUBLIC_KEY_REQUEST); - } else { - _mProcessNextDAppsRequest.postValue( - BraveWalletDAppsActivity.ActivityType.FINISH); - } - }); - } - } - - public void clearDappsState() { - _mProcessNextDAppsRequest.postValue(null); - } - - public void processDecryptRequest(boolean isApproved, Origin origin) { - synchronized (mLock) { - if (mBraveWalletService == null) { - return; - } - mBraveWalletService.notifyDecryptRequestProcessed(isApproved, origin); - mBraveWalletService.getPendingDecryptRequests(requests -> { - if (requests != null && requests.length > 0) { - _mProcessNextDAppsRequest.postValue( - BraveWalletDAppsActivity.ActivityType.DECRYPT_REQUEST); - } else { - _mProcessNextDAppsRequest.postValue( - BraveWalletDAppsActivity.ActivityType.FINISH); - } - }); - } - } - public List getSupportedCryptoAccountTypes() { List cryptoAccountTypeInfos = new ArrayList<>(); if (isSolanaEnabled()) { diff --git a/android/java/org/chromium/chrome/browser/app/domain/DappsModel.java b/android/java/org/chromium/chrome/browser/app/domain/DappsModel.java index 7769cd49dfa..4f20bcf7ee6 100644 --- a/android/java/org/chromium/chrome/browser/app/domain/DappsModel.java +++ b/android/java/org/chromium/chrome/browser/app/domain/DappsModel.java @@ -7,20 +7,22 @@ package org.chromium.chrome.browser.app.domain; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Transformations; import org.chromium.brave_wallet.mojom.AccountInfo; -import org.chromium.brave_wallet.mojom.BraveWalletConstants; import org.chromium.brave_wallet.mojom.BraveWalletService; import org.chromium.brave_wallet.mojom.CoinType; import org.chromium.brave_wallet.mojom.JsonRpcService; import org.chromium.brave_wallet.mojom.KeyringService; +import org.chromium.brave_wallet.mojom.SignAllTransactionsRequest; +import org.chromium.brave_wallet.mojom.SignTransactionRequest; import org.chromium.brave_wallet.mojom.TransactionInfo; import org.chromium.brave_wallet.mojom.TransactionStatus; +import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletDAppsActivity; import org.chromium.chrome.browser.crypto_wallet.util.PendingTxHelper; import org.chromium.chrome.browser.crypto_wallet.util.Utils; import org.chromium.mojo.bindings.Callbacks; import org.chromium.mojo.system.Pair; +import org.chromium.url.internal.mojom.Origin; import java.util.ArrayList; import java.util.Arrays; @@ -34,8 +36,16 @@ public class DappsModel { private PendingTxHelper mPendingTxHelper; private final MutableLiveData _mWalletIconNotificationVisible = new MutableLiveData<>(false); - public final LiveData mWalletIconNotificationVisible = _mWalletIconNotificationVisible; private final Object mLock = new Object(); + private final MutableLiveData _mProcessNextDAppsRequest = + new MutableLiveData<>(); + private final MutableLiveData> _mSignTxRequests; + private final MutableLiveData> _mSignAllTxRequests; + private final LiveData> mSignTxRequests; + private final LiveData> mSignAllTxRequests; + public final LiveData mWalletIconNotificationVisible = _mWalletIconNotificationVisible; + public final LiveData mProcessNextDAppsRequest = + _mProcessNextDAppsRequest; public DappsModel(JsonRpcService jsonRpcService, BraveWalletService braveWalletService, KeyringService keyringService, PendingTxHelper pendingTxHelper) { @@ -43,6 +53,10 @@ public class DappsModel { mJsonRpcService = jsonRpcService; mKeyringService = keyringService; mPendingTxHelper = pendingTxHelper; + _mSignTxRequests = new MutableLiveData<>(Collections.emptyList()); + mSignTxRequests = _mSignTxRequests; + _mSignAllTxRequests = new MutableLiveData<>(Collections.emptyList()); + mSignAllTxRequests = _mSignAllTxRequests; } public void fetchAccountsForConnectionReq(@CoinType.EnumType int coinType, @@ -74,6 +88,44 @@ public class DappsModel { } } + public LiveData> fetchSignTxRequest() { + mBraveWalletService.getPendingSignTransactionRequests(requests -> { + _mSignTxRequests.postValue(new ArrayList<>(Arrays.asList(requests))); + }); + return mSignTxRequests; + } + + public void signTxRequest(boolean isApproved, SignTransactionRequest signTransactionRequest) { + mBraveWalletService.notifySignTransactionRequestProcessed( + isApproved, signTransactionRequest.id, null, null); + mBraveWalletService.getPendingSignTransactionRequests(requests -> { + if (requests.length == 0) { + _mProcessNextDAppsRequest.postValue(BraveWalletDAppsActivity.ActivityType.FINISH); + } else { + _mSignTxRequests.postValue(new ArrayList<>(Arrays.asList(requests))); + } + }); + } + + public LiveData> fetchSignAllTxRequest() { + mBraveWalletService.getPendingSignAllTransactionsRequests(requests -> { + _mSignAllTxRequests.postValue(new ArrayList<>(Arrays.asList(requests))); + }); + return mSignAllTxRequests; + } + + public void signAllTxRequest(boolean isApproved, SignAllTransactionsRequest request) { + mBraveWalletService.notifySignAllTransactionsRequestProcessed( + isApproved, request.id, null, null); + mBraveWalletService.getPendingSignAllTransactionsRequests(requests -> { + if (requests.length == 0) { + _mProcessNextDAppsRequest.postValue(BraveWalletDAppsActivity.ActivityType.FINISH); + } else { + _mSignAllTxRequests.postValue(new ArrayList<>(Arrays.asList(requests))); + } + }); + } + public void resetServices(JsonRpcService jsonRpcService, BraveWalletService braveWalletService, PendingTxHelper pendingTxHelper) { synchronized (mLock) { @@ -89,6 +141,46 @@ public class DappsModel { } } + public void clearDappsState() { + _mProcessNextDAppsRequest.postValue(null); + } + + public void processPublicEncryptionKey(boolean isApproved, Origin origin) { + synchronized (mLock) { + if (mBraveWalletService == null) { + return; + } + mBraveWalletService.notifyGetPublicKeyRequestProcessed(isApproved, origin); + mBraveWalletService.getPendingGetEncryptionPublicKeyRequests(requests -> { + if (requests != null && requests.length > 0) { + _mProcessNextDAppsRequest.postValue(BraveWalletDAppsActivity.ActivityType + .GET_ENCRYPTION_PUBLIC_KEY_REQUEST); + } else { + _mProcessNextDAppsRequest.postValue( + BraveWalletDAppsActivity.ActivityType.FINISH); + } + }); + } + } + + public void processDecryptRequest(boolean isApproved, Origin origin) { + synchronized (mLock) { + if (mBraveWalletService == null) { + return; + } + mBraveWalletService.notifyDecryptRequestProcessed(isApproved, origin); + mBraveWalletService.getPendingDecryptRequests(requests -> { + if (requests != null && requests.length > 0) { + _mProcessNextDAppsRequest.postValue( + BraveWalletDAppsActivity.ActivityType.DECRYPT_REQUEST); + } else { + _mProcessNextDAppsRequest.postValue( + BraveWalletDAppsActivity.ActivityType.FINISH); + } + }); + } + } + public void setWalletBadgeVisible() { _mWalletIconNotificationVisible.setValue(true); } @@ -107,37 +199,41 @@ public class DappsModel { mBraveWalletService.getPendingSignMessageRequests(requests -> { if (requests != null && requests.length > 0) { setWalletBadgeVisible(); - return; } }); mBraveWalletService.getPendingAddSuggestTokenRequests(requests -> { if (requests != null && requests.length > 0) { setWalletBadgeVisible(); - return; } }); mBraveWalletService.getPendingGetEncryptionPublicKeyRequests(requests -> { if (requests != null && requests.length > 0) { setWalletBadgeVisible(); - return; } }); mJsonRpcService.getPendingAddChainRequests(networks -> { if (networks != null && networks.length > 0) { setWalletBadgeVisible(); - return; } }); mJsonRpcService.getPendingSwitchChainRequests(requests -> { if (requests != null && requests.length > 0) { setWalletBadgeVisible(); - return; } }); mBraveWalletService.getPendingDecryptRequests(requests -> { if (requests != null && requests.length > 0) { setWalletBadgeVisible(); - return; + } + }); + mBraveWalletService.getPendingSignTransactionRequests(requests -> { + if (requests != null && requests.length > 0) { + setWalletBadgeVisible(); + } + }); + mBraveWalletService.getPendingSignAllTransactionsRequests(requests -> { + if (requests != null && requests.length > 0) { + setWalletBadgeVisible(); } }); for (TransactionInfo info : mPendingTxHelper.mTransactionInfoLd.getValue()) { 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 2bdc8b73d88..cee111cebe7 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 @@ -26,6 +26,7 @@ import org.chromium.chrome.browser.crypto_wallet.fragments.dapps.AddTokenFragmen import org.chromium.chrome.browser.crypto_wallet.fragments.dapps.ConnectAccountFragment; import org.chromium.chrome.browser.crypto_wallet.fragments.dapps.EncryptionKeyFragment; import org.chromium.chrome.browser.crypto_wallet.fragments.dapps.SignMessageFragment; +import org.chromium.chrome.browser.crypto_wallet.fragments.dapps.SignTransactionFragment; import org.chromium.chrome.browser.crypto_wallet.listeners.TransactionConfirmationListener; import org.chromium.chrome.browser.crypto_wallet.util.PendingTxHelper; import org.chromium.chrome.browser.crypto_wallet.util.TransactionUtils; @@ -53,7 +54,9 @@ public class BraveWalletDAppsActivity extends BraveWalletBaseActivity CONFIRM_TRANSACTION(5), DECRYPT_REQUEST(6), GET_ENCRYPTION_PUBLIC_KEY_REQUEST(7), - FINISH(8); + SIGN_TRANSACTION(8), + SIGN_ALL_TRANSACTIONS(9), + FINISH(10); private int value; private static Map map = new HashMap<>(); @@ -88,7 +91,7 @@ public class BraveWalletDAppsActivity extends BraveWalletBaseActivity BraveActivity activity = BraveActivity.getBraveActivity(); if (activity != null) { mWalletModel = activity.getWalletModel(); - mWalletModel.getCryptoModel().mProcessNextDAppsRequest.observe(this, activityType -> { + mWalletModel.getDappsModel().mProcessNextDAppsRequest.observe(this, activityType -> { if (activityType == null) return; switch (activityType) { case GET_ENCRYPTION_PUBLIC_KEY_REQUEST: @@ -214,6 +217,9 @@ public class BraveWalletDAppsActivity extends BraveWalletBaseActivity } else if (mActivityType == ActivityType.ADD_ETHEREUM_CHAIN || mActivityType == ActivityType.SWITCH_ETHEREUM_CHAIN) { mFragment = new AddSwitchChainNetworkFragment(mActivityType, this); + } else if (mActivityType == ActivityType.SIGN_TRANSACTION + || mActivityType == ActivityType.SIGN_ALL_TRANSACTIONS) { + mFragment = SignTransactionFragment.newInstance(mActivityType); } else if (mActivityType == ActivityType.CONNECT_ACCOUNT) { mFragment = new ConnectAccountFragment(); } else if (mActivityType == GET_ENCRYPTION_PUBLIC_KEY_REQUEST @@ -239,7 +245,7 @@ public class BraveWalletDAppsActivity extends BraveWalletBaseActivity // (under-development) and get rid of explicit clear state call BraveActivity activity = BraveActivity.getBraveActivity(); if (activity != null) { - activity.getWalletModel().getCryptoModel().clearDappsState(); + activity.getWalletModel().getDappsModel().clearDappsState(); } if (mPendingTxHelper != null) { mPendingTxHelper.destroy(); 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 522ee4f37b1..98faa84eda1 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 @@ -17,27 +17,55 @@ import org.chromium.chrome.R; import java.util.List; -public class TwoLineItemRecyclerViewAdapter - extends RecyclerView.Adapter { - private final List mValues; +public class TwoLineItemRecyclerViewAdapter extends RecyclerView.Adapter { + private final List mValues; - public TwoLineItemRecyclerViewAdapter(List items) { + public TwoLineItemRecyclerViewAdapter(List items) { mValues = items; } @NonNull @Override - public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + if (viewType == TwoLineItem.TYPE_DIVIDER) { + return new ViewHolderDivider( + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_fragment_two_line_divider_item, parent, false)); + } return new ViewHolder( LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_fragment_two_line_item, parent, false)); } @Override - public void onBindViewHolder(final ViewHolder holder, int position) { - holder.mItem = mValues.get(position); - holder.mTvTitle.setText(mValues.get(position).getTitle()); - holder.mTvSubtitle.setText(mValues.get(position).getSubTitle()); + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + TwoLineItem twoLineItem = mValues.get(position); + if (twoLineItem.getType() == TwoLineItem.TYPE_TEXT) { + ViewHolder viewHolder = (ViewHolder) holder; + viewHolder.mTvSubtitle.setVisibility(View.VISIBLE); + viewHolder.mItem = twoLineItem; + TwoLineItemText itemDataSourceText = (TwoLineItemText) twoLineItem; + if (itemDataSourceText.getTitle() == null) { + viewHolder.mTvTitle.setVisibility(View.GONE); + } else { + viewHolder.mTvTitle.setText(itemDataSourceText.getTitle()); + } + if (itemDataSourceText.getSubTitle() == null) { + viewHolder.mTvSubtitle.setVisibility(View.GONE); + } else { + viewHolder.mTvSubtitle.setText(itemDataSourceText.getSubTitle()); + } + } else if (twoLineItem.getType() == TwoLineItem.TYPE_HEADER) { + ViewHolder viewHolder = (ViewHolder) holder; + TwoLineItemHeader itemDataSourceHeader = (TwoLineItemHeader) twoLineItem; + viewHolder.mTvTitle.setText(itemDataSourceHeader.mHeader); + viewHolder.mTvSubtitle.setVisibility(View.GONE); + } + } + + @Override + public int getItemViewType(int position) { + return mValues.get(position).getType(); } @Override @@ -48,26 +76,33 @@ public class TwoLineItemRecyclerViewAdapter public static class ViewHolder extends RecyclerView.ViewHolder { public final TextView mTvTitle; public final TextView mTvSubtitle; - public TwoLineItemDataSource mItem; + public TwoLineItem mItem; public ViewHolder(View itemView) { super(itemView); mTvTitle = itemView.findViewById(R.id.item_fragment_two_line_title); mTvSubtitle = itemView.findViewById(R.id.item_fragment_two_line_sub_title); } - - @NonNull - @Override - public String toString() { - return mTvTitle.toString() + " '" + mTvSubtitle.getText() + "'"; - } } - public static class TwoLineItemDataSource { + public static class ViewHolderDivider extends RecyclerView.ViewHolder { + public ViewHolderDivider(@NonNull View itemView) { + super(itemView); + } + } + public interface TwoLineItem { + int TYPE_TEXT = 1; + int TYPE_HEADER = 2; + int TYPE_DIVIDER = 3; + + int getType(); + } + + public static class TwoLineItemText implements TwoLineItem { private String title; private String subTitle; - public TwoLineItemDataSource(String title, String subTitle) { + public TwoLineItemText(String title, String subTitle) { this.title = title; this.subTitle = subTitle; } @@ -87,5 +122,30 @@ public class TwoLineItemRecyclerViewAdapter public void setSubTitle(String subTitle) { this.subTitle = subTitle; } + + @Override + public int getType() { + return TYPE_TEXT; + } + } + + public static class TwoLineItemDivider implements TwoLineItem { + @Override + public int getType() { + return TYPE_DIVIDER; + } + } + + public static class TwoLineItemHeader implements TwoLineItem { + public String mHeader; + + public TwoLineItemHeader(String mHeader) { + this.mHeader = mHeader; + } + + @Override + public int getType() { + return TYPE_HEADER; + } } } 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 60e796c1181..9340353a138 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 @@ -16,7 +16,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.chromium.chrome.R; import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter; -import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItemDataSource; +import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItem; import java.util.List; @@ -25,14 +25,14 @@ import java.util.List; * sub-title. */ public class TwoLineItemFragment extends Fragment { - private List items; + private List items; private TwoLineItemRecyclerViewAdapter adapter; - public TwoLineItemFragment(List items) { + public TwoLineItemFragment(List items) { this.items = items; } - public static TwoLineItemFragment newInstance(List items) { + public static TwoLineItemFragment newInstance(List items) { TwoLineItemFragment fragment = new TwoLineItemFragment(items); return fragment; } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/AddSwitchChainNetworkFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/AddSwitchChainNetworkFragment.java index b658f0272fa..c7789e06901 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/AddSwitchChainNetworkFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/AddSwitchChainNetworkFragment.java @@ -3,13 +3,10 @@ package org.chromium.chrome.browser.crypto_wallet.fragments.dapps; import static org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletDAppsActivity.ActivityType.ADD_ETHEREUM_CHAIN; import static org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletDAppsActivity.ActivityType.SWITCH_ETHEREUM_CHAIN; -import android.app.Activity; import android.graphics.Bitmap; import android.os.Bundle; -import android.text.SpannableString; import android.text.Spanned; import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -33,10 +30,10 @@ import org.chromium.brave_wallet.mojom.SwitchChainRequest; import org.chromium.chrome.R; import org.chromium.chrome.browser.app.BraveActivity; import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletBaseActivity; -import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletDAppsActivity; import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletDAppsActivity.ActivityType; import org.chromium.chrome.browser.crypto_wallet.adapters.FragmentNavigationItemAdapter; -import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItemDataSource; +import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItem; +import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItemText; import org.chromium.chrome.browser.crypto_wallet.fragments.TwoLineItemFragment; import org.chromium.chrome.browser.crypto_wallet.util.NavigationItem; import org.chromium.chrome.browser.crypto_wallet.util.Utils; @@ -58,21 +55,21 @@ public class AddSwitchChainNetworkFragment extends BaseDAppsFragment { private AddChainRequest mAddChainRequest; private AddSwitchRequestProcessListener mAddSwitchRequestProcessListener; private boolean hasMultipleAddSwitchChainRequest; - private List networks; - private List details; + private List networks; + private List details; private ImageView mFavicon; private TextView mSiteTv; private FaviconHelper mFaviconHelper; private DefaultFaviconHelper mDefaultFaviconHelper; - public AddSwitchChainNetworkFragment(BraveWalletDAppsActivity.ActivityType panelType) { + public AddSwitchChainNetworkFragment(ActivityType panelType) { mPanelType = panelType; mTabTitles = new ArrayList<>(); networks = new ArrayList<>(); details = new ArrayList<>(); } - public AddSwitchChainNetworkFragment(BraveWalletDAppsActivity.ActivityType panelType, + public AddSwitchChainNetworkFragment(ActivityType panelType, AddSwitchRequestProcessListener addSwitchRequestProcessListener) { this(panelType); mAddSwitchRequestProcessListener = addSwitchRequestProcessListener; @@ -260,28 +257,26 @@ public class AddSwitchChainNetworkFragment extends BaseDAppsFragment { private void addDetailsTabInfo(NetworkInfo networkInfo) { networks.clear(); - networks.add(new TwoLineItemDataSource( + networks.add(new TwoLineItemText( getString(R.string.brave_wallet_allow_add_network_name), networkInfo.chainName)); - networks.add( - new TwoLineItemDataSource(getString(R.string.brave_wallet_allow_add_network_url), - getActiveRpcEndpointUrl(networkInfo))); + networks.add(new TwoLineItemText(getString(R.string.brave_wallet_allow_add_network_url), + getActiveRpcEndpointUrl(networkInfo))); } private void addNetworkTabInfo(NetworkInfo networkInfo) { details.clear(); - details.add(new TwoLineItemDataSource( + details.add(new TwoLineItemText( getString(R.string.brave_wallet_allow_add_network_name), networkInfo.chainName)); - details.add( - new TwoLineItemDataSource(getString(R.string.brave_wallet_allow_add_network_url), - getActiveRpcEndpointUrl(networkInfo))); - details.add(new TwoLineItemDataSource( + details.add(new TwoLineItemText(getString(R.string.brave_wallet_allow_add_network_url), + getActiveRpcEndpointUrl(networkInfo))); + details.add(new TwoLineItemText( getString(R.string.brave_wallet_allow_add_network_chain_id), networkInfo.chainId)); - details.add(new TwoLineItemDataSource( + details.add(new TwoLineItemText( getString(R.string.brave_wallet_allow_add_network_currency_symbol), networkInfo.symbol)); - details.add(new TwoLineItemDataSource(getString(R.string.wallet_add_custom_asset_decimals), + details.add(new TwoLineItemText(getString(R.string.wallet_add_custom_asset_decimals), String.valueOf(networkInfo.decimals))); - details.add(new TwoLineItemDataSource( + details.add(new TwoLineItemText( getString(R.string.brave_wallet_add_network_block_explorer_urls), networkInfo.blockExplorerUrls.length > 0 ? networkInfo.blockExplorerUrls[0] : "")); } diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/EncryptionKeyFragment.java b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/EncryptionKeyFragment.java index f2b9e54161c..65b71987c1f 100644 --- a/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/EncryptionKeyFragment.java +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/EncryptionKeyFragment.java @@ -155,10 +155,10 @@ public class EncryptionKeyFragment extends Fragment implements View.OnClickListe public void onClick(View v) { if (mActivityType == BraveWalletDAppsActivity.ActivityType.GET_ENCRYPTION_PUBLIC_KEY_REQUEST) { - mWalletModel.getCryptoModel().processPublicEncryptionKey( + mWalletModel.getDappsModel().processPublicEncryptionKey( isPositiveActionTriggered(v), mEncryptionPublicKeyRequest.originInfo.origin); } else if (mActivityType == BraveWalletDAppsActivity.ActivityType.DECRYPT_REQUEST) { - mWalletModel.getCryptoModel().processDecryptRequest( + mWalletModel.getDappsModel().processDecryptRequest( isPositiveActionTriggered(v), mDecryptRequest.originInfo.origin); } } 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 new file mode 100644 index 00000000000..bbe241395ef --- /dev/null +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/fragments/dapps/SignTransactionFragment.java @@ -0,0 +1,364 @@ +/* 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.dapps; + +import android.content.res.ColorStateList; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +import org.chromium.brave_wallet.mojom.CoinType; +import org.chromium.brave_wallet.mojom.SignAllTransactionsRequest; +import org.chromium.brave_wallet.mojom.SignTransactionRequest; +import org.chromium.brave_wallet.mojom.SolanaInstruction; +import org.chromium.brave_wallet.mojom.SolanaTxData; +import org.chromium.chrome.R; +import org.chromium.chrome.browser.app.BraveActivity; +import org.chromium.chrome.browser.app.domain.WalletModel; +import org.chromium.chrome.browser.crypto_wallet.activities.BraveWalletDAppsActivity.ActivityType; +import org.chromium.chrome.browser.crypto_wallet.adapters.FragmentNavigationItemAdapter; +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.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItemText; +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.util.NavigationItem; +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; +import org.chromium.chrome.browser.util.TabUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class SignTransactionFragment extends BaseDAppsBottomSheetDialogFragment { + private static final String PARAM_ACITITY_TYPE = "sign_param"; + private List mTabTitles; + private List mSignTransactionRequests; + private List mSignAllTransactionRequests; + private ViewPager2 mViewPager; + private TabLayout mTabLayout; + private ImageView mAccountImage; + private TextView mAccountName; + private TextView mNetworkName; + private Button mBtCancel; + private Button mBtSign; + private TextView mWebSite; + private ExecutorService mExecutor; + private Handler mHandler; + private List details; + private LinearLayout mWarningLl; + private TextView mTxLearnMore; + private TextView mTvTxCounter; + private ActivityType mActivityType; + private WalletModel mWalletModel; + private SignTx mSignTxStep = SignTx.SIGN_RISK; + private int mTxRequestNumber; + private SignTransactionRequest mSignTransactionRequest; + private SignAllTransactionsRequest mSignAllTransactionsRequest; + private final View.OnClickListener onNextTxClick = v -> { + incrementCounter(); + updateSignDataAndDetails(); + }; + + private Button mBtnCounterNext; + private List mTxDatas; + + public static SignTransactionFragment newInstance(ActivityType activityType) { + SignTransactionFragment fragment = new SignTransactionFragment(); + Bundle args = new Bundle(); + args.putSerializable(PARAM_ACITITY_TYPE, activityType); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mExecutor = Executors.newSingleThreadExecutor(); + mHandler = new Handler(Looper.getMainLooper()); + details = new ArrayList<>(); + mTabTitles = new ArrayList<>(); + mTabTitles.add( + new NavigationItem(getString(R.string.details), new TwoLineItemFragment(details))); + mSignTransactionRequests = Collections.emptyList(); + mSignAllTransactionRequests = Collections.emptyList(); + BraveActivity activity = BraveActivity.getBraveActivity(); + if (activity != null) { + mWalletModel = activity.getWalletModel(); + } + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_sign_tx_message, container, false); + mViewPager = view.findViewById(R.id.fragment_sign_tx_msg_tv_message_view_pager); + mTabLayout = view.findViewById(R.id.fragment_sign_tx_msg_tv_message_tabs); + mAccountImage = view.findViewById(R.id.fragment_sign_tx_msg_cv_iv_account); + mAccountName = view.findViewById(R.id.fragment_sign_tx_msg_tv_account_name); + mNetworkName = view.findViewById(R.id.fragment_sign_tx_msg_tv_network_name); + mViewPager.setUserInputEnabled(false); + FragmentNavigationItemAdapter adapter = new FragmentNavigationItemAdapter(this, mTabTitles); + mViewPager.setAdapter(adapter); + new TabLayoutMediator(mTabLayout, mViewPager, + (tab, position) -> tab.setText(mTabTitles.get(position).getTitle())) + .attach(); + + mTvTxCounter = view.findViewById(R.id.fragment_sign_tx_msg_tv_counter); + mBtnCounterNext = view.findViewById(R.id.fragment_encryption_msg_btn_next); + mWarningLl = view.findViewById(R.id.fragment_sign_tx_warning_container); + mTxLearnMore = view.findViewById(R.id.fragment_sign_tx_warning_learn_more); + mBtCancel = view.findViewById(R.id.fragment_sign_tx_msg_btn_cancel); + mBtSign = view.findViewById(R.id.fragment_sign_tx_msg_btn_sign); + mWebSite = view.findViewById(R.id.domain); + initComponents(); + + return view; + } + + private void initComponents() { + assert getArguments() != null; + mActivityType = (ActivityType) getArguments().getSerializable(PARAM_ACITITY_TYPE); + updateTxPanelPerStep(); + updateAccount(); + updateNetwork(); + fetchSignRequestData(); + Spanned associatedSPLTokenAccountInfo = + Utils.createSpanForSurroundedPhrase(requireContext(), R.string.learn_more, (v) -> { + TabUtils.openUrlInNewTab(false, WalletConstants.URL_SIGN_TRANSACTION_REQUEST); + TabUtils.bringChromeTabbedActivityToTheTop(getActivity()); + }); + mTxLearnMore.setMovementMethod(LinkMovementMethod.getInstance()); + mTxLearnMore.setText(associatedSPLTokenAccountInfo); + + mBtSign.setOnClickListener(v -> { processRequest(true); }); + mBtCancel.setOnClickListener(v -> { processRequest(false); }); + mTvTxCounter.setOnClickListener(onNextTxClick); + mBtnCounterNext.setOnClickListener(onNextTxClick); + } + + // Update tx counter UI based on the current value of mTxRequestNumber + private void updateSignRequestData() { + if (!mSignTransactionRequests.isEmpty() && !mSignAllTransactionRequests.isEmpty()) { + return; + } + int size = 0; + switch (mActivityType) { + case SIGN_TRANSACTION: + if (mTxRequestNumber >= mSignTransactionRequests.size()) { + mTxRequestNumber = 0; + } + mSignTransactionRequest = mSignTransactionRequests.get(mTxRequestNumber); + size = mSignTransactionRequests.size(); + mTxDatas = Arrays.asList(TransactionUtils.safeSolData(mSignTransactionRequest)); + break; + case SIGN_ALL_TRANSACTIONS: + if (mTxRequestNumber >= mSignAllTransactionRequests.size()) { + mTxRequestNumber = 0; + } + mSignAllTransactionsRequest = mSignAllTransactionRequests.get(mTxRequestNumber); + size = mSignAllTransactionRequests.size(); + mTxDatas = TransactionUtils.safeSolData(mSignAllTransactionsRequest); + break; + default: // Do nothing + } + if (size == 1) { + mTvTxCounter.setVisibility(View.GONE); + mBtnCounterNext.setVisibility(View.GONE); + } else { + if (mSignTxStep != SignTx.SIGN_RISK) { + mTvTxCounter.setVisibility(View.VISIBLE); + mBtnCounterNext.setVisibility(View.VISIBLE); + } + } + mTvTxCounter.setText( + getString(R.string.brave_wallet_queue_of, (mTxRequestNumber + 1), size)); + updateActionState(mTxRequestNumber == 0); + } + + private void updateSignDetails() { + if (mTxDatas != null && !mTxDatas.isEmpty()) { + details.clear(); + for (SolanaTxData txData : mTxDatas) { + for (SolanaInstruction solanaInstruction : txData.instructions) { + details.add(new TwoLineItemText( + TransactionUtils.getSolanaProgramIdName( + solanaInstruction.programId, requireContext()) + + " - " + + getString( + TransactionUtils.getSolType(solanaInstruction.programId, + solanaInstruction.decodedData.instructionType)), + null)); + SolanaInstructionPresenter solanaInstructionPresenter = + new SolanaInstructionPresenter(solanaInstruction); + if (solanaInstructionPresenter.mIsUnknown) { + details.add(new TwoLineItemRecyclerViewAdapter.TwoLineItemHeader( + getString(R.string.accounts))); + } + details.addAll(solanaInstructionPresenter.accountDataToList()); + if (solanaInstructionPresenter.mIsUnknown) { + details.add(new TwoLineItemRecyclerViewAdapter.TwoLineItemHeader( + getString(R.string.brave_wallet_data_text))); + } + details.addAll(solanaInstructionPresenter.accountParamDataToList()); + if (mTxDatas.size() > 1 || txData.instructions.length > 1) { + details.add(new TwoLineItemRecyclerViewAdapter.TwoLineItemDivider()); + } + } + } + for (NavigationItem navigationItem : mTabTitles) { + ((TwoLineItemFragment) navigationItem.getFragment()).invalidateData(); + } + } + } + + private void incrementCounter() { + switch (mActivityType) { + case SIGN_TRANSACTION: + if (mSignTransactionRequests.size() == 0) return; + if (mTxRequestNumber >= mSignTransactionRequests.size()) { + mTxRequestNumber = 0; + return; + } + break; + case SIGN_ALL_TRANSACTIONS: + if (mSignAllTransactionRequests.size() == 0) return; + if (mTxRequestNumber >= mSignAllTransactionRequests.size()) { + mTxRequestNumber = 0; + return; + } + break; + default: // Do nothing + } + mTxRequestNumber++; + } + + private void updateActionState(boolean isEnabled) { + if (mSignTxStep == SignTx.SIGN_RISK) return; + mBtCancel.setEnabled(isEnabled); + mBtSign.setEnabled(isEnabled); + if (isEnabled) { + mBtSign.setBackgroundTintList(ColorStateList.valueOf( + ContextCompat.getColor(requireContext(), R.color.brave_action_color))); + + } else { + mBtSign.setBackgroundTintList(ColorStateList.valueOf( + ContextCompat.getColor(requireContext(), R.color.modern_grey_700))); + } + } + + private void fetchSignRequestData() { + switch (mActivityType) { + case SIGN_TRANSACTION: + mWalletModel.getDappsModel().fetchSignTxRequest().observe( + getViewLifecycleOwner(), requests -> { + if (requests.size() == 0) return; + mSignTransactionRequests = requests; + updateSignDataAndDetails(); + }); + break; + case SIGN_ALL_TRANSACTIONS: + mWalletModel.getDappsModel().fetchSignAllTxRequest().observe( + getViewLifecycleOwner(), requests -> { + if (requests.size() == 0) return; + mSignAllTransactionRequests = requests; + updateSignDataAndDetails(); + }); + break; + default: // Do nothing + } + } + + private void updateTxPanelPerStep() { + if (mSignTxStep == SignTx.SIGN_RISK) { + mViewPager.setVisibility(View.GONE); + mTvTxCounter.setVisibility(View.GONE); + mBtnCounterNext.setVisibility(View.GONE); + mWarningLl.setVisibility(View.VISIBLE); + mBtSign.setBackgroundTintList(ColorStateList.valueOf( + ContextCompat.getColor(requireContext(), R.color.brave_theme_error))); + mBtSign.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); + mBtSign.setText(R.string.continue_text); + } else if (mSignTxStep == SignTx.SIGN_TX) { + mWarningLl.setVisibility(View.GONE); + mViewPager.setVisibility(View.VISIBLE); + mBtSign.setBackgroundTintList(ColorStateList.valueOf( + ContextCompat.getColor(requireContext(), R.color.brave_action_color))); + mBtSign.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_key, 0, 0, 0); + mBtSign.setText(R.string.brave_wallet_sign_message_positive_button_action); + } + } + + private void processRequest(boolean isApproved) { + if (isApproved) { + // Continue clicked, update ui + if (mSignTxStep == SignTx.SIGN_RISK) { + mSignTxStep = SignTx.SIGN_TX; + updateTxPanelPerStep(); + updateSignDataAndDetails(); + return; + } + } + switch (mActivityType) { + case SIGN_TRANSACTION: + mWalletModel.getDappsModel().signTxRequest(isApproved, mSignTransactionRequest); + break; + case SIGN_ALL_TRANSACTIONS: + mWalletModel.getDappsModel().signAllTxRequest( + isApproved, mSignAllTransactionsRequest); + break; + default: // Do nothing + } + } + + private void updateAccount() { + BraveActivity activity = BraveActivity.getBraveActivity(); + if (activity != null) { + activity.getWalletModel() + .getKeyringModel() + .getSelectedAccountOrAccountPerOrigin() + .observe(getViewLifecycleOwner(), accountInfo -> { + if (accountInfo == null) return; + Utils.setBlockiesBitmapResource( + mExecutor, mHandler, mAccountImage, accountInfo.address, true); + String accountText = accountInfo.name + "\n" + accountInfo.address; + mAccountName.setText(accountText); + }); + } + } + + private void updateNetwork() { + getJsonRpcService().getNetwork(CoinType.ETH, + selectedNetwork -> { mNetworkName.setText(selectedNetwork.chainName); }); + } + + private void updateSignDataAndDetails() { + updateSignRequestData(); + updateSignDetails(); + } + + private enum SignTx { SIGN_RISK, SIGN_TX } +} diff --git a/android/java/org/chromium/chrome/browser/crypto_wallet/model/SolanaInstructionPresenter.java b/android/java/org/chromium/chrome/browser/crypto_wallet/model/SolanaInstructionPresenter.java new file mode 100644 index 00000000000..b99e6eb39cb --- /dev/null +++ b/android/java/org/chromium/chrome/browser/crypto_wallet/model/SolanaInstructionPresenter.java @@ -0,0 +1,127 @@ +/* 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.model; + +import org.chromium.brave_wallet.mojom.CoinType; +import org.chromium.brave_wallet.mojom.SolanaAccountMeta; +import org.chromium.brave_wallet.mojom.SolanaInstruction; +import org.chromium.brave_wallet.mojom.SolanaInstructionAccountParam; +import org.chromium.brave_wallet.mojom.SolanaInstructionParam; +import org.chromium.chrome.browser.crypto_wallet.adapters.TwoLineItemRecyclerViewAdapter.TwoLineItemText; +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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class SolanaInstructionPresenter { + private boolean isDecodedDataPresent; + public List mAccountDatas; + private SolanaInstruction mSolanaInstruction; + public boolean mIsUnknown; + + public SolanaInstructionPresenter(SolanaInstruction solanaInstruction) { + assert solanaInstruction != null : "solanaInstruction is null"; + mIsUnknown = TransactionUtils.isSolTxUnknown(solanaInstruction.programId); + mSolanaInstruction = solanaInstruction; + mAccountDatas = new ArrayList<>(); + if (solanaInstruction.accountMetas != null) { + if (!mIsUnknown) { + isDecodedDataPresent = solanaInstruction.decodedData != null; + boolean isAccountParamsPresent = + isDecodedDataPresent && solanaInstruction.decodedData.accountParams != null; + int accountParamLen = isAccountParamsPresent + ? solanaInstruction.decodedData.accountParams.length + : 0; + boolean isSignerPresent = false; + for (int i = 0; i < solanaInstruction.accountMetas.length; i++) { + SolanaAccountMeta solanaAccountMeta = solanaInstruction.accountMetas[i]; + SolanaInstructionAccountParam solanaInstructionAccountParam = null; + if (isDecodedDataPresent && isAccountParamsPresent) { + if (i < accountParamLen) { + // Add all accounts with corresponding labels (e.g. From, To) if a label + // exists + solanaInstructionAccountParam = + solanaInstruction.decodedData.accountParams[i]; + isSignerPresent = + solanaInstructionAccountParam.name.equalsIgnoreCase("signers"); + } else if (isSignerPresent) { + // Out of labels, add as signer if present + solanaInstructionAccountParam = + solanaInstruction.decodedData + .accountParams[accountParamLen - 1]; + } + } + // Add all types of accounts from 'accountsMetas' with specific roles from + // solanaInstructionAccountParam If there are more accounts (m) than roles (n) + // then only add the extra accounts if last role was signers, + // [signers] = accounts[n-1...m] + if (i < accountParamLen || isSignerPresent) { + mAccountDatas.add(new SolanaInstructionAccountPresenter( + solanaAccountMeta.pubkey, solanaInstructionAccountParam)); + } + } + } + } + } + + public List accountDataToList() { + List twoLineItemDataSources = new ArrayList<>(); + if (mIsUnknown) { + for (SolanaAccountMeta solanaAccountMeta : getSolanaInstruction().accountMetas) { + twoLineItemDataSources.add(new TwoLineItemText(null, solanaAccountMeta.pubkey)); + } + } else { + for (SolanaInstructionAccountPresenter accountPresenter : mAccountDatas) { + twoLineItemDataSources.add(new TwoLineItemText( + accountPresenter.mLocalizeAccountHeader, accountPresenter.mPubKey)); + } + } + return twoLineItemDataSources; + } + + public List accountParamDataToList() { + List twoLineItemDataSources = new ArrayList<>(); + if (isDecodedDataPresent && mSolanaInstruction.decodedData.params != null) { + for (SolanaInstructionParam instructionParam : mSolanaInstruction.decodedData.params) { + String value = instructionParam.value; + if (instructionParam.name.equalsIgnoreCase(WalletConstants.SOL_LAMPORTS)) { + value = String.format(Locale.getDefault(), "%.9f", + Utils.getBalanceForCoinType( + CoinType.SOL, Utils.SOL_DEFAULT_DECIMALS, value)) + + " " + WalletConstants.SOL; + } + twoLineItemDataSources.add( + new TwoLineItemText(instructionParam.localizedName, value)); + } + } + return twoLineItemDataSources; + } + + public static class SolanaInstructionAccountPresenter { + public String mPubKey; + public String mLocalizeAccountHeader; + public String mAccountHeader; + + public SolanaInstructionAccountPresenter( + String pubKey, SolanaInstructionAccountParam solInsAccountParam) { + this.mPubKey = pubKey; + if (solInsAccountParam == null) { + mLocalizeAccountHeader = ""; + mAccountHeader = ""; + return; + } + mLocalizeAccountHeader = solInsAccountParam.localizedName; + mAccountHeader = solInsAccountParam.name; + } + } + + public SolanaInstruction getSolanaInstruction() { + return mSolanaInstruction; + } +} 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 49617af713e..e9affba53f4 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 @@ -5,8 +5,23 @@ package org.chromium.chrome.browser.crypto_wallet.util; +import android.content.Context; +import android.text.TextUtils; + +import androidx.annotation.StringRes; + import org.chromium.brave_wallet.mojom.CoinType; +import org.chromium.brave_wallet.mojom.SignAllTransactionsRequest; +import org.chromium.brave_wallet.mojom.SignTransactionRequest; +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.TxDataUnion; +import org.chromium.chrome.R; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; public class TransactionUtils { public static @CoinType.EnumType int getCoinFromTxDataUnion(TxDataUnion txDataUnion) { @@ -17,4 +32,159 @@ public class TransactionUtils { else return CoinType.ETH; } + + // ---------- Solana ---------- + public static String getSolanaProgramIdName(String programId, Context context) { + if (TextUtils.isEmpty(programId)) return ""; + switch (programId) { + case WalletConstants.SOL_INS_SYSTEM: + return context.getString(R.string.brave_wallet_solana_system_program); + case WalletConstants.SOL_INS_CONFIG: + return context.getString(R.string.brave_wallet_solana_config_program); + case WalletConstants.SOL_INS_STAKE: + return context.getString(R.string.brave_wallet_solana_stake_program); + case WalletConstants.SOL_INS_VOTE: + return context.getString(R.string.brave_wallet_solana_vote_program); + case WalletConstants.SOL_INS_BPF: + return context.getString(R.string.brave_wallet_solana_bp_floader); + case WalletConstants.SOL_INS_SIG_VERIFY: + return context.getString(R.string.brave_wallet_solana_ed25519_program); + case WalletConstants.SOL_INS_TOKEN: + return context.getString(R.string.brave_wallet_solana_token_program); + case WalletConstants.SOL_INS_SECP: + return context.getString(R.string.brave_wallet_solana_secp256k1_program); + default: + return programId; + } + } + + public static int getSolType(String programId, int instructionType) { + if (TextUtils.isEmpty(programId)) return R.string.brave_wallet_unknown; + + switch (programId) { + case WalletConstants.SOL_INS_SYSTEM: + return getSolInsSysName(instructionType); + case WalletConstants.SOL_INS_TOKEN: + return getSolInsTokenName(instructionType); + default: + return R.string.brave_wallet_unknown; + } + } + + public static boolean isSolTxUnknown(String programId) { + if (TextUtils.isEmpty(programId)) return true; + switch (programId) { + case WalletConstants.SOL_INS_SYSTEM: + case WalletConstants.SOL_INS_TOKEN: + return false; + default: + return true; + } + } + + public static SolanaTxData safeSolData(SignTransactionRequest request) { + return safeSolData(request.txData); + } + + public static List safeSolData(SignAllTransactionsRequest request) { + if (request == null || request.txDatas == null) return Collections.emptyList(); + List txDatas = new ArrayList<>(); + for (TxDataUnion txDataUnion : request.txDatas) { + SolanaTxData txData = safeSolData(txDataUnion); + if (txData != null) { + txDatas.add(txData); + } + } + return txDatas; + } + + public static SolanaTxData safeSolData(TxDataUnion txDataUnion) { + if (txDataUnion != null && txDataUnion.which() == TxDataUnion.Tag.SolanaTxData) { + return txDataUnion.getSolanaTxData(); + } + return null; + } + + public static @StringRes int getSolInsSysName(int type) { + switch (type) { + case SolanaSystemInstruction.CREATE_ACCOUNT: + return R.string.brave_wallet_tx_create_account; + case SolanaSystemInstruction.ASSIGN: + return R.string.brave_wallet_tx_assign; + case SolanaSystemInstruction.TRANSFER: + return R.string.brave_wallet_tx_transfer; + case SolanaSystemInstruction.CREATE_ACCOUNT_WITH_SEED: + return R.string.brave_wallet_tx_create_account_with_seed; + case SolanaSystemInstruction.ADVANCE_NONCE_ACCOUNT: + return R.string.brave_wallet_tx_advance_nonce_account; + case SolanaSystemInstruction.WITHDRAW_NONCE_ACCOUNT: + return R.string.brave_wallet_tx_withdraw_nonce_account; + case SolanaSystemInstruction.INITIALIZE_NONCE_ACCOUNT: + return R.string.brave_wallet_tx_initialize_nonce_account; + case SolanaSystemInstruction.AUTHORIZE_NONCE_ACCOUNT: + return R.string.brave_wallet_tx_authorize_nonce_account; + case SolanaSystemInstruction.ALLOCATE: + return R.string.brave_wallet_tx_allocate; + case SolanaSystemInstruction.ALLOCATE_WITH_SEED: + return R.string.brave_wallet_tx_allocate_with_seed; + case SolanaSystemInstruction.ASSIGN_WITH_SEED: + return R.string.brave_wallet_tx_assign_with_seed; + case SolanaSystemInstruction.TRANSFER_WITH_SEED: + return R.string.brave_wallet_tx_transfer_with_seed; + case SolanaSystemInstruction.UPGRADE_NONCE_ACCOUNT: + return R.string.brave_wallet_tx_upgrade_nonce_account; + default: + return R.string.brave_wallet_unknown; + } + } + + public static @StringRes int getSolInsTokenName(int type) { + switch (type) { + case SolanaTokenInstruction.INITIALIZE_MINT: + return R.string.brave_wallet_tx_initialize_mint; + case SolanaTokenInstruction.INITIALIZE_ACCOUNT: + return R.string.brave_wallet_tx_initialize_account; + case SolanaTokenInstruction.INITIALIZE_MULTISIG: + return R.string.brave_wallet_tx_initialize_multisig; + case SolanaTokenInstruction.TRANSFER: + return R.string.brave_wallet_tx_transfer; + case SolanaTokenInstruction.APPROVE: + return R.string.brave_wallet_tx_approve; + case SolanaTokenInstruction.REVOKE: + return R.string.brave_wallet_tx_revoke; + case SolanaTokenInstruction.SET_AUTHORITY: + return R.string.brave_wallet_tx_set_authority; + case SolanaTokenInstruction.MINT_TO: + return R.string.brave_wallet_tx_mint_to; + case SolanaTokenInstruction.BURN: + return R.string.brave_wallet_tx_burn; + case SolanaTokenInstruction.CLOSE_ACCOUNT: + return R.string.brave_wallet_tx_close_account; + case SolanaTokenInstruction.FREEZE_ACCOUNT: + return R.string.brave_wallet_tx_freeze_account; + case SolanaTokenInstruction.THAW_ACCOUNT: + return R.string.brave_wallet_tx_thaw_account; + case SolanaTokenInstruction.TRANSFER_CHECKED: + return R.string.brave_wallet_tx_transfer_checked; + case SolanaTokenInstruction.APPROVE_CHECKED: + return R.string.brave_wallet_tx_approve_checked; + case SolanaTokenInstruction.MINT_TO_CHECKED: + return R.string.brave_wallet_tx_mint_to_checked; + case SolanaTokenInstruction.BURN_CHECKED: + return R.string.brave_wallet_tx_burn_checked; + case SolanaTokenInstruction.INITIALIZE_ACCOUNT2: + return R.string.brave_wallet_tx_initialize_account2; + case SolanaTokenInstruction.SYNC_NATIVE: + return R.string.brave_wallet_tx_sync_native; + case SolanaTokenInstruction.INITIALIZE_ACCOUNT3: + return R.string.brave_wallet_tx_initialize_account3; + case SolanaTokenInstruction.INITIALIZE_MULTISIG2: + return R.string.brave_wallet_tx_initialize_multisig2; + case SolanaTokenInstruction.INITIALIZE_MINT2: + return R.string.brave_wallet_tx_initialize_mint2; + default: + return R.string.brave_wallet_unknown; + } + } + // ---------- Solana ---------- } 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 da8ce87a225..653ce7a8b29 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,6 +1,7 @@ package org.chromium.chrome.browser.crypto_wallet.util; import org.chromium.brave_wallet.mojom.BraveWalletConstants; +import org.chromium.chrome.R; import java.util.Arrays; import java.util.List; @@ -16,6 +17,10 @@ public final class WalletConstants { "https://doc.aurora.dev/bridge/bridge-overview/"; public static final String URL_RAINBOW_BRIDGE_RISKS = "https://rainbowbridge.app/approvals"; + // Brave + public static final String URL_SIGN_TRANSACTION_REQUEST = + "https://support.brave.com/hc/en-us/articles/4409513799693"; + // Regex public static final String REGX_ANY_ETH_ADDRESS = ".*(0x[a-fA-F0-9]{40}).*"; @@ -61,4 +66,18 @@ public final class WalletConstants { public static List SWAP_SUPPORTED_NETWORKS = Arrays.asList( BraveWalletConstants.MAINNET_CHAIN_ID, BraveWalletConstants.GOERLI_CHAIN_ID); + + // Solana + public static final String SOL_LAMPORTS = "lamports"; + public static final String SOL = "SOL"; + + // Solana instruction types + public static final String SOL_INS_SYSTEM = BraveWalletConstants.SOLANA_SYSTEM_PROGRAM_ID; + public static final String SOL_INS_TOKEN = BraveWalletConstants.SOLANA_TOKEN_PROGRAM_ID; + public static final String SOL_INS_CONFIG = "Config1111111111111111111111111111111111111"; + public static final String SOL_INS_STAKE = "Stake11111111111111111111111111111111111111"; + public static final String SOL_INS_VOTE = "Vote111111111111111111111111111111111111111"; + public static final String SOL_INS_BPF = "BPFLoaderUpgradeab1e11111111111111111111111"; + public static final String SOL_INS_SIG_VERIFY = "Ed25519SigVerify111111111111111111111111111"; + public static final String SOL_INS_SECP = "KeccakSecp256k11111111111111111111111111111"; } diff --git a/android/java/res/drawable/crypto_wallet_blue_button.xml b/android/java/res/drawable/crypto_wallet_blue_button.xml index c0a886a4eb9..abac7a0dac8 100644 --- a/android/java/res/drawable/crypto_wallet_blue_button.xml +++ b/android/java/res/drawable/crypto_wallet_blue_button.xml @@ -1,14 +1,14 @@ - + - + - + @@ -18,7 +18,7 @@ - + diff --git a/android/java/res/drawable/ic_warning.xml b/android/java/res/drawable/ic_warning.xml new file mode 100644 index 00000000000..54c6964738e --- /dev/null +++ b/android/java/res/drawable/ic_warning.xml @@ -0,0 +1,10 @@ + + + diff --git a/android/java/res/layout/fragment_sign_tx_message.xml b/android/java/res/layout/fragment_sign_tx_message.xml new file mode 100644 index 00000000000..4d827d39798 --- /dev/null +++ b/android/java/res/layout/fragment_sign_tx_message.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/java/res/layout/item_fragment_two_line_divider_item.xml b/android/java/res/layout/item_fragment_two_line_divider_item.xml new file mode 100644 index 00000000000..bb47a151111 --- /dev/null +++ b/android/java/res/layout/item_fragment_two_line_divider_item.xml @@ -0,0 +1,7 @@ + + diff --git a/android/java/res/values/brave_colors.xml b/android/java/res/values/brave_colors.xml index c85734bebe9..9a31c30b987 100644 --- a/android/java/res/values/brave_colors.xml +++ b/android/java/res/values/brave_colors.xml @@ -35,6 +35,7 @@ #868E96 #cc0000 #51cf66 + #E32444 #fb542b diff --git a/android/java/res/values/brave_styles.xml b/android/java/res/values/brave_styles.xml index e725ee11ecc..a454fa4f75a 100644 --- a/android/java/res/values/brave_styles.xml +++ b/android/java/res/values/brave_styles.xml @@ -77,7 +77,18 @@ 16sp center - + + +