diff --git a/app/build.gradle b/app/build.gradle index 2be7b1a8f2..a0118326c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,7 +24,7 @@ tasks.withType(Test).configureEach { } detekt { - toolVersion = "1.20.0-RC1" + toolVersion = "1.23.5" buildUponDefaultConfig = true // preconfigure defaults allRules = false // activate all available (even unstable) rules. baseline = file("$projectDir/check/detekt-baseline.xml") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7802b1102f..63bd3fef89 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -123,10 +123,6 @@ android:name=".ui.SplashActivity" android:theme="@style/AppTheme.NoActionBar.Splash" /> - - + for (i in (feeHistory.gasUsedRatio.size - 1) downTo 0) { if (feeHistory.gasUsedRatio[i] > 0.9) { baseFee[i] = baseFee[i + 1] } } + /*((feeHistory.gasUsedRatio.size - 1) downTo 0).forEach { i -> + if (feeHistory.gasUsedRatio[i] > 0.9) { + baseFee[i] = baseFee[i + 1] + } + }*/ + val order = (0..feeHistory.gasUsedRatio.size).map { it }.sortedBy { baseFee[it] } var maxBaseFee = ZERO val result = mutableMapOf() - (maxTimeFactor downTo 0).forEach { timeFactor -> + for (timeFactor in maxTimeFactor downTo 0) { var bf: BigInteger if (timeFactor < 1e-6) { bf = baseFee.last() @@ -145,8 +151,8 @@ internal fun suggestPriorityFee(firstBlock: Long, feeHistory: FeeHistory, gasSer rewardPercentile.toString()).blockingGet(); val rewardSize = feeHistoryFetch?.reward?.size ?: 0 - (0 until rewardSize).forEach { - rewards.add(BigInteger(Numeric.cleanHexPrefix(feeHistoryFetch.reward[it][0].removePrefix("0x")), + for (index in 0 until rewardSize) { + rewards.add(BigInteger(Numeric.cleanHexPrefix(feeHistoryFetch.reward[index][0].removePrefix("0x")), 16)) } if (rewardSize < blockCount) break diff --git a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java index 81a88f256c..21863c5fea 100644 --- a/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java +++ b/app/src/main/java/com/alphawallet/app/interact/WalletConnectInteract.java @@ -6,21 +6,17 @@ import android.os.IBinder; import com.alphawallet.app.entity.WalletConnectActions; -import com.alphawallet.app.entity.lifi.Token; import com.alphawallet.app.entity.walletconnect.WalletConnectSessionItem; import com.alphawallet.app.entity.walletconnect.WalletConnectV2SessionItem; import com.alphawallet.app.repository.entity.RealmWCSession; import com.alphawallet.app.service.RealmManager; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.viewmodel.WalletConnectViewModel; import com.alphawallet.app.walletconnect.WCClient; import com.alphawallet.app.walletconnect.entity.WCUtils; import com.walletconnect.web3.wallet.client.Wallet; import com.walletconnect.web3.wallet.client.Web3Wallet; -import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.inject.Inject; diff --git a/app/src/main/java/com/alphawallet/app/service/WalletConnectService.java b/app/src/main/java/com/alphawallet/app/service/WalletConnectService.java deleted file mode 100644 index 910dc5f936..0000000000 --- a/app/src/main/java/com/alphawallet/app/service/WalletConnectService.java +++ /dev/null @@ -1,514 +0,0 @@ -package com.alphawallet.app.service; - -import static com.alphawallet.app.C.WALLET_CONNECT_ADD_CHAIN; -import static com.alphawallet.app.C.WALLET_CONNECT_CLIENT_TERMINATE; -import static com.alphawallet.app.C.WALLET_CONNECT_COUNT_CHANGE; -import static com.alphawallet.app.C.WALLET_CONNECT_FAIL; -import static com.alphawallet.app.C.WALLET_CONNECT_NEW_SESSION; -import static com.alphawallet.app.C.WALLET_CONNECT_REQUEST; -import static com.alphawallet.app.C.WALLET_CONNECT_SWITCH_CHAIN; - -import android.app.Service; -import android.content.Intent; -import android.os.Binder; -import android.os.IBinder; -import android.text.TextUtils; - -import androidx.annotation.Nullable; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import com.alphawallet.app.C; -import com.alphawallet.app.entity.WalletConnectActions; -import com.alphawallet.app.entity.walletconnect.SignType; -import com.alphawallet.app.entity.walletconnect.WCRequest; -import com.alphawallet.app.walletconnect.WCClient; -import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject; - -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.TimeUnit; - -import io.reactivex.Observable; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import kotlin.Unit; -import timber.log.Timber; - -/** - * The purpose of this service is to manage the currently active WalletConnect sessions. Keep the connections alive and terminate where required. - * Doing this in an activity means the connection objects are subject to activity lifecycle events (Destroy etc). - */ -public class WalletConnectService extends Service -{ - private final static ConcurrentHashMap clientMap = new ConcurrentHashMap<>(); - private final static ConcurrentLinkedQueue signRequests = new ConcurrentLinkedQueue<>(); - private final static ConcurrentHashMap clientTimes = new ConcurrentHashMap<>(); - private WCRequest currentRequest = null; - - private static final String TAG = "WCClientSvs"; - - @Nullable - private Disposable messagePump; - - @Override - public int onStartCommand(Intent intent, int flags, int startId) - { - Timber.tag(TAG).d("SERVICE STARTING"); - - if (intent == null) - { - return Service.START_STICKY; - } - - try - { - int actionVal = Integer.parseInt(intent.getAction()); - WalletConnectActions action = WalletConnectActions.values()[actionVal]; - - switch (action) - { - case CONNECT: - Timber.tag(TAG).d("SERVICE CONNECT"); - break; - case APPROVE: - approveRequest(intent); - break; - case REJECT: - rejectRequest(intent); - break; - case DISCONNECT: - Timber.tag(TAG).d("SERVICE DISCONNECT"); - //kill any active connection - disconnectCurrentSessions(); - break; - case CLOSE: - Timber.tag(TAG).d("SERVICE CLOSE"); - //result.getData().getIntExtra(C.EXTRA_CHAIN_ID, -1); - String sessionId = intent.getStringExtra("session"); - disconnectSession(sessionId); - break; - case MSG_PUMP: - Timber.tag(TAG).d("SERVICE MSG PUMP"); - checkMessages(); - break; - case SWITCH_CHAIN: - Timber.tag(TAG).d("SERVICE SWITCH CHAIN"); - switchChain(intent); - break; - case ADD_CHAIN: - Timber.tag(TAG).d("SERVICE ADD CHAIN"); - addChain(intent); - break; - } - } - catch (Exception e) - { - Timber.e(e); - } - return START_STICKY; - } - - private final IBinder mBinder = new LocalBinder(); - - public WCRequest getPendingRequest(String sessionId) - { - WCRequest request = signRequests.poll(); - if (request != null && !request.sessionId.equals(sessionId)) - { - signRequests.add(request); // not for this client, put it back on the stack, at the back - request = null; - } - else if (request != null) - { - currentRequest = request; - } - - return request; - } - - public int getConnectionCount() - { - return clientMap.size(); - } - - public WCRequest getCurrentRequest() - { - return currentRequest; - } - - private void disconnectCurrentSessions() - { - for (WCClient client : clientMap.values()) - { - if (client.isConnected()) - { - client.killSession(); - } - } - } - - private void disconnectSession(String sessionId) - { - if (TextUtils.isEmpty(sessionId)) - { - disconnectCurrentSessions(); - } - else - { - WCClient client = clientMap.get(sessionId); - if (client != null) client.killSession(); - } - } - - //executed a pending request, remove from the queue - public void removePendingRequest(long id) - { - for (WCRequest rq : signRequests) - { - if (rq.id == id) - { - signRequests.remove(rq); - break; - } - } - } - - public class LocalBinder extends Binder - { - public WalletConnectService getService() - { - return WalletConnectService.this; - } - } - - @Nullable - private Disposable pingTimer; - - @Nullable - @Override - public IBinder onBind(Intent intent) - { - return mBinder; - } - - public WCClient getClient(String sessionId) - { - if (sessionId == null) - { - return null; - } - else - { - return clientMap.get(sessionId); - } - } - - public void putClient(String sessionId, WCClient client) - { - Timber.tag(TAG).d("Add session: %s", sessionId); - clientMap.put(sessionId, client); - broadcastConnectionCount(clientMap.size()); - clientTimes.put(sessionId, System.currentTimeMillis()); - setupClient(client); - startSessionPinger(); - } - - public void addClients(List clientList) - { - for (WCClient client : clientList) - { - String sessionId = client.sessionId(); - if (sessionId != null && clientMap.get(sessionId) == null) - { - Timber.d("WC: Add client: %s", sessionId); - putClient(sessionId, client); - } - } - } - - private void rejectRequest(Intent intent) - { - String sessionId = intent.getStringExtra("sessionId"); - long id = intent.getLongExtra("id", 0L); - String message = intent.getStringExtra("message"); - - WCClient c = clientMap.get(sessionId); - if (c != null && c.isConnected()) - { - c.rejectRequest(id, message); - } - } - - private void approveRequest(Intent intent) - { - String sessionId = intent.getStringExtra("sessionId"); - long id = intent.getLongExtra("id", 0L); - String message = intent.getStringExtra("message"); - - WCClient c = clientMap.get(sessionId); - - if (c != null && c.isConnected()) - { - c.approveRequest(id, message); - } - } - - private void setupClient(WCClient client) - { - client.setOnSessionRequest((id, peer) -> { - if (client.sessionId() == null) return Unit.INSTANCE; - setLastUsed(client); - signRequests.add(new WCRequest(client.sessionId(), id, peer, client.chainIdVal())); - broadcastSessionEvent(WALLET_CONNECT_NEW_SESSION, client.sessionId()); - Timber.tag(TAG).d("On Request: %s", peer.getName()); - return Unit.INSTANCE; - }); - - client.setOnFailure(throwable -> { - //alert UI - if (client.sessionId() == null) return Unit.INSTANCE; - Timber.tag(TAG).d("On Fail: %s", throwable.getMessage()); - //only add if no errors already in queue - if (queueHasNoErrors()) - { - signRequests.add(new WCRequest(client.sessionId(), throwable, client.chainIdVal())); - broadcastSessionEvent(WALLET_CONNECT_FAIL, client.sessionId()); - } - startMessagePump(); - return Unit.INSTANCE; - }); - - client.setOnDisconnect((code, reason) -> { - Timber.tag(TAG).d("Terminate session?"); - terminateClient(client.sessionId()); - client.resetState(); - return Unit.INSTANCE; - }); - - client.setOnEthSign((id, message) -> { - if (client.sessionId() == null) return Unit.INSTANCE; - setLastUsed(client); - WCRequest rq = new WCRequest(client.sessionId(), id, message); - Timber.tag(TAG).d("Sign Request: %s", message.toString()); - sendRequest(client, rq); - return Unit.INSTANCE; - }); - - client.setOnEthSignTransaction((id, transaction) -> { - if (client.sessionId() == null) return Unit.INSTANCE; - setLastUsed(client); - WCRequest rq = new WCRequest(client.sessionId(), id, transaction, true, client.chainIdVal()); - sendRequest(client, rq); - return Unit.INSTANCE; - }); - - client.setOnEthSendTransaction((id, transaction) -> { - if (client.sessionId() == null) return Unit.INSTANCE; - setLastUsed(client); - WCRequest rq = new WCRequest(client.sessionId(), id, transaction, false, client.chainIdVal()); - sendRequest(client, rq); - return Unit.INSTANCE; - }); - - client.setOnSwitchEthereumChain((requestId, chainId) -> { - Timber.tag(TAG).d("onSwitchEthereumChain: request.id: %s, sessionId: %s, chainId: %s", requestId, client.sessionId(), chainId); - // send broadcast to show dialog for switching chain - Intent i = new Intent(WALLET_CONNECT_SWITCH_CHAIN); - i.putExtra(C.EXTRA_WC_REQUEST_ID, requestId); - i.putExtra(C.EXTRA_SESSION_ID, client.sessionId()); - i.putExtra(C.EXTRA_CHAIN_ID, chainId); - i.putExtra(C.EXTRA_NAME, client.getPeerMeta().getName()); - sendWalletConnectBroadcast(i); - return Unit.INSTANCE; - }); - - client.setOnAddEthereumChain((requestId, chainObj) -> { - Timber.tag(TAG).d("onAddEthereumChain: requestId: %s, chainObj: %s", requestId, chainObj); - Intent i = new Intent(WALLET_CONNECT_ADD_CHAIN); - i.putExtra(C.EXTRA_WC_REQUEST_ID, requestId); - i.putExtra(C.EXTRA_SESSION_ID, client.sessionId()); - i.putExtra(C.EXTRA_CHAIN_OBJ, chainObj); - sendWalletConnectBroadcast(i); - return Unit.INSTANCE; - }); - } - - private void sendWalletConnectBroadcast(Intent i) - { - LocalBroadcastManager.getInstance(this).sendBroadcast(i); - } - - //TODO: Can we determine if AlphaWallet is running? If it is, no need to add this to the queue, - //TODO: as user will get the intent in walletConnectActionReceiver (repeat for below) - private void sendRequest(WCClient client, WCRequest rq) - { - Timber.d("sendRequest: sessionId: %s", client.sessionId()); - signRequests.add(rq); - //see if this connection is live, if so then bring WC request to foreground - switchToWalletConnectApprove(client.sessionId(), rq); - startMessagePump(); - } - - private void broadcastSessionEvent(String command, String sessionId) - { - Timber.d("broadcastSessionEvent: sessionId: %s, command: %s", sessionId, command); - Intent intent = new Intent(command); - intent.putExtra("sessionid", sessionId); - intent.putExtra("wcrequest", getPendingRequest(sessionId)); // pass WCRequest as parcelable in the intent - sendWalletConnectBroadcast(intent); - } - - private void broadcastConnectionCount(int count) - { - Intent intent = new Intent(WALLET_CONNECT_COUNT_CHANGE); - intent.putExtra("count", count); - sendWalletConnectBroadcast(intent); - } - - private void switchToWalletConnectApprove(String sessionId, WCRequest rq) - { - WCClient cc = clientMap.get(sessionId); - - if (cc != null) - { - Intent intent = new Intent(WALLET_CONNECT_REQUEST); - intent.putExtra("sessionid", sessionId); - intent.putExtra("wcrequest", rq); - sendWalletConnectBroadcast(intent); - - Timber.tag(TAG).d("Connected clients: %s", clientMap.size()); - } - } - - private void startSessionPinger() - { - if (pingTimer == null || pingTimer.isDisposed()) - { - pingTimer = Observable.interval(0, 30, TimeUnit.SECONDS) - .doOnNext(l -> ping()).subscribe(); - } - } - - private void ping() - { - for (String sessionKey : clientMap.keySet()) - { - WCClient c = clientMap.get(sessionKey); - if (c == null) return; - if (c.isConnected() && c.chainIdVal() != 0 && c.getAccounts() != null) - { - Timber.tag(TAG).d("Ping Key: %s", sessionKey); - c.updateSession(c.getAccounts(), c.chainIdVal(), true); - } - } - } - - public void terminateClient(String sessionKey) - { - broadcastSessionEvent(WALLET_CONNECT_CLIENT_TERMINATE, sessionKey); - clientMap.remove(sessionKey); - broadcastConnectionCount(clientMap.size()); - if (clientMap.size() == 0 && pingTimer != null && !pingTimer.isDisposed()) - { - Timber.tag(TAG).d("Stop timer & service"); - pingTimer.dispose(); - pingTimer = null; - stopSelf(); - } - } - - private void setLastUsed(WCClient c) - { - String sessionId = c.sessionId(); - if (sessionId != null) clientTimes.put(sessionId, System.currentTimeMillis()); - } - - private void startMessagePump() - { - if (messagePump != null && !messagePump.isDisposed()) messagePump.dispose(); - - messagePump = Observable.interval(2000, 2000, TimeUnit.MILLISECONDS) - .doOnNext(l -> checkMessages()) - .observeOn(Schedulers.newThread()).subscribe(); - } - - private void checkMessages() - { - WCRequest rq = signRequests.peek(); - if (rq != null) - { - WCClient cc = clientMap.get(rq.sessionId); - if (cc != null) - { - switchToWalletConnectApprove(rq.sessionId, rq); - } - } - else if (messagePump != null && !messagePump.isDisposed()) - { - messagePump.dispose(); - } - } - - private boolean queueHasNoErrors() - { - for (WCRequest rq : signRequests.toArray(new WCRequest[0])) - { - if (rq.type == SignType.FAILURE) return false; - } - - return true; - } - - private void switchChain(Intent intent) - { - long requestId = intent.getLongExtra(C.EXTRA_WC_REQUEST_ID, -1); - String id = intent.getStringExtra(C.EXTRA_SESSION_ID); - long chainId = intent.getLongExtra(C.EXTRA_CHAIN_ID, -1); - boolean approved = intent.getBooleanExtra(C.EXTRA_APPROVED, false); - boolean chainAvailable = intent.getBooleanExtra(C.EXTRA_CHAIN_AVAILABLE, true); - Timber.tag(TAG).d("sessionId: %s, chainId: %s, approved: %s", id, chainId, approved); - if (requestId != -1 && id != null && chainId != -1) - { - WCClient c = clientMap.get(id); - if (c != null) - { - c.switchChain(requestId, chainId, approved, chainAvailable); - } - else - { - Timber.tag(TAG).d("WCClient not found"); - } - } - } - - public void addChain(Intent intent) - { - long requestId = intent.getLongExtra(C.EXTRA_WC_REQUEST_ID, -1); - String id = intent.getStringExtra(C.EXTRA_SESSION_ID); - WalletAddEthereumChainObject chainObject = intent.getParcelableExtra(C.EXTRA_CHAIN_OBJ); - boolean chainAdded = intent.getBooleanExtra(C.EXTRA_APPROVED, false); - Timber.tag(TAG).d("sessionId: %s, chainObj: %s, chainAdded: %s", id, chainObject, chainAdded); - - if (requestId != -1) - { - WCClient c = clientMap.get(id); - if (c != null) - { - if (chainObject != null) - { - c.addChain(requestId, chainObject, chainAdded); - } - else - { - // reject with serverError - c.addChain(requestId, chainObject, false); - } - } - else - { - Timber.tag(TAG).d("WCClient not found"); - } - } - } -} diff --git a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java index 080b4eb2eb..212b8f7f26 100644 --- a/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java +++ b/app/src/main/java/com/alphawallet/app/ui/DappBrowserFragment.java @@ -83,7 +83,6 @@ import com.alphawallet.app.repository.TokensRealmSource; import com.alphawallet.app.repository.entity.RealmToken; import com.alphawallet.app.service.GasService; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.ui.QRScanning.QRScannerActivity; import com.alphawallet.app.ui.widget.OnDappHomeNavClickListener; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; diff --git a/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java b/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java index 6fa50fd33d..d4d65d1916 100644 --- a/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/HomeActivity.java @@ -570,7 +570,7 @@ protected void onResume() { super.onResume(); setWCConnect(); - viewModel.prepare(this); + viewModel.prepare(); viewModel.getWalletName(this); viewModel.setErrorCallback(this); if (homeReceiver == null) @@ -913,15 +913,6 @@ public void resetTransactions() getFragment(ACTIVITY).resetTransactions(); } - @Override - public void openWalletConnect(String sessionId) - { - Intent intent = new Intent(getApplication(), WalletConnectActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - intent.putExtra("session", sessionId); - startActivity(intent); - } - private void hideDialog() { if (dialog != null && dialog.isShowing()) diff --git a/app/src/main/java/com/alphawallet/app/ui/QRScanning/QRScannerActivity.java b/app/src/main/java/com/alphawallet/app/ui/QRScanning/QRScannerActivity.java index 3945e9f5bc..ad1ef3d414 100644 --- a/app/src/main/java/com/alphawallet/app/ui/QRScanning/QRScannerActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/QRScanning/QRScannerActivity.java @@ -26,7 +26,6 @@ import com.alphawallet.app.entity.analytics.QrScanResultType; import com.alphawallet.app.entity.analytics.QrScanSource; import com.alphawallet.app.ui.BaseActivity; -import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.viewmodel.QrScannerViewModel; import com.alphawallet.app.walletconnect.util.WalletConnectHelper; @@ -189,18 +188,8 @@ private void displayErrorDialog(String title, String errorMessage) private void startWalletConnect(String qrCode) { - Intent intent; - if (WalletConnectHelper.isWalletConnectV1(qrCode)) - { - intent = new Intent(this, WalletConnectActivity.class); - intent.putExtra("qrCode", qrCode); - intent.putExtra(C.EXTRA_CHAIN_ID, chainIdOverride); - } - else - { - intent = new Intent(this, WalletConnectV2Activity.class); - intent.putExtra("url", qrCode); - } + Intent intent = new Intent(this, WalletConnectV2Activity.class); + intent.putExtra("url", qrCode); startActivity(intent); setResult(WALLET_CONNECT); finish(); @@ -209,6 +198,7 @@ private void startWalletConnect(String qrCode) @Override public void onBackPressed() { + super.onBackPressed(); viewModel.track(Analytics.Action.SCAN_QR_CODE_CANCELLED); Intent intent = new Intent(); setResult(Activity.RESULT_CANCELED, intent); diff --git a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java index 6441f2da80..181b1d7c98 100644 --- a/app/src/main/java/com/alphawallet/app/ui/SendActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/SendActivity.java @@ -289,16 +289,6 @@ else if (requestCode == C.BARCODE_READER_REQUEST_CODE) } } - private void startWalletConnect(String qrCode) - { - Intent intent = new Intent(this, WalletConnectActivity.class); - intent.putExtra("qrCode", qrCode); - intent.putExtra(C.EXTRA_CHAIN_ID, token.tokenInfo.chainId); - startActivity(intent); - setResult(RESULT_OK); - finish(); - } - private void showCameraDenied() { if (dialog != null && dialog.isShowing()) dialog.dismiss(); diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletConnectActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletConnectActivity.java deleted file mode 100644 index 288e257240..0000000000 --- a/app/src/main/java/com/alphawallet/app/ui/WalletConnectActivity.java +++ /dev/null @@ -1,1565 +0,0 @@ -package com.alphawallet.app.ui; - -import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.view.MenuItem; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.ViewModelProvider; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import com.alphawallet.app.C; -import com.alphawallet.app.R; -import com.alphawallet.app.analytics.Analytics; -import com.alphawallet.app.entity.ActionSheetStatus; -import com.alphawallet.app.entity.AnalyticsProperties; -import com.alphawallet.app.entity.CryptoFunctions; -import com.alphawallet.app.entity.NetworkInfo; -import com.alphawallet.app.entity.SignAuthenticationCallback; -import com.alphawallet.app.entity.StandardFunctionInterface; -import com.alphawallet.app.entity.TransactionReturn; -import com.alphawallet.app.entity.Wallet; -import com.alphawallet.app.entity.WalletType; -import com.alphawallet.app.entity.analytics.ActionSheetSource; -import com.alphawallet.app.entity.tokens.Token; -import com.alphawallet.app.entity.walletconnect.SignType; -import com.alphawallet.app.entity.walletconnect.WCRequest; -import com.alphawallet.app.repository.EthereumNetworkBase; -import com.alphawallet.app.repository.SignRecord; -import com.alphawallet.app.service.GasService; -import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; -import com.alphawallet.app.viewmodel.WalletConnectViewModel; -import com.alphawallet.app.walletconnect.AWWalletConnectClient; -import com.alphawallet.app.walletconnect.WCClient; -import com.alphawallet.app.walletconnect.WCSession; -import com.alphawallet.app.walletconnect.entity.WCEthereumSignMessage; -import com.alphawallet.app.walletconnect.entity.WCEthereumTransaction; -import com.alphawallet.app.walletconnect.entity.WCPeerMeta; -import com.alphawallet.app.walletconnect.entity.WCUtils; -import com.alphawallet.app.walletconnect.entity.WalletConnectCallback; -import com.alphawallet.app.web3.entity.Address; -import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject; -import com.alphawallet.app.web3.entity.Web3Transaction; -import com.alphawallet.app.widget.AWalletAlertDialog; -import com.alphawallet.app.widget.ActionSheet; -import com.alphawallet.app.widget.ActionSheetDialog; -import com.alphawallet.app.widget.ActionSheetSignDialog; -import com.alphawallet.app.widget.ChainName; -import com.alphawallet.app.widget.FunctionButtonBar; -import com.alphawallet.app.widget.SignTransactionDialog; -import com.alphawallet.app.widget.TokenIcon; -import com.alphawallet.hardware.SignatureFromKey; -import com.alphawallet.token.entity.EthereumMessage; -import com.alphawallet.token.entity.EthereumTypedMessage; -import com.alphawallet.token.entity.SignMessageType; -import com.alphawallet.token.entity.Signable; -import com.bumptech.glide.Glide; -import com.google.gson.Gson; - -import org.jetbrains.annotations.NotNull; -import org.web3j.utils.Numeric; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.UUID; - -import javax.inject.Inject; - -import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; -import kotlin.Unit; -import timber.log.Timber; - -@AndroidEntryPoint -public class WalletConnectActivity extends BaseActivity implements ActionSheetCallback, StandardFunctionInterface, WalletConnectCallback -{ - public static final String WC_LOCAL_PREFIX = "wclocal:"; - public static final String WC_INTENT = "wcintent:"; - private static final String TAG = "WCClient"; - private static final String DEFAULT_ICON = "https://example.walletconnect.org/favicon.ico"; - private static final long CONNECT_TIMEOUT = 10 * DateUtils.SECOND_IN_MILLIS; // 10 Seconds timeout - private final Handler handler = new Handler(Looper.getMainLooper()); - private final long switchChainDialogCallbackId = 1; - private WalletConnectViewModel viewModel; - private LocalBroadcastManager broadcastManager; - private WCClient client; - private WCSession session; - private WCPeerMeta peerMeta; - private WCPeerMeta remotePeerMeta; - private ActionSheet confirmationDialog; - ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> confirmationDialog.setCurrentGasIndex(result)); - private AddEthereumChainPrompt addEthereumChainPrompt; - // data for switch chain request - private long switchChainRequestId; // rpc request id - private long switchChainId; // new chain to switch to - private String name; // remote peer name - private String currentSessionId; // sessionId for which chain is switched - private boolean chainAvailable; // flag denoting chain available in AW or not - private ImageView icon; - private TextView peerName; - private TextView peerUrl; - private TextView statusText; - private TextView textName; - private TextView txCount; - private ChainName chainName; - private TokenIcon chainIcon; - private ProgressBar progressBar; - private LinearLayout infoLayout; - private LinearLayout txCountLayout; - private FunctionButtonBar functionBar; - private boolean fromDappBrowser = false; //if using this from dappBrowser (which is a bit strange but could happen) then return back to browser once signed - private boolean fromPhoneBrowser = false; //if from phone browser, clicking 'back' should take user back to dapp running on the phone's browser, - // -- according to WalletConnect's expected UX docs. - private boolean fromSessionActivity = false; - private String qrCode; - private SignAuthenticationCallback signCallback; - private long lastId; - private String signData; - private WCEthereumSignMessage.WCSignType signType; - private long chainIdOverride; - - @Inject - AWWalletConnectClient awWalletConnectClient; - - ActivityResultLauncher getNetwork = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getData() == null) return; - chainIdOverride = result.getData().getLongExtra(C.EXTRA_CHAIN_ID, MAINNET_ID); - confirmationDialog.updateChain(chainIdOverride); - }); - private boolean waitForWalletConnectSession = false; - private long requestId = 0; - private AWalletAlertDialog dialog = null; - private final BroadcastReceiver walletConnectActionReceiver = new BroadcastReceiver() - { - @Override - public void onReceive(Context context, Intent intent) - { - Timber.tag(TAG).d("Received message"); - String action = intent.getAction(); - switch (action) - { - case C.WALLET_CONNECT_REQUEST: - case C.WALLET_CONNECT_NEW_SESSION: - case C.WALLET_CONNECT_FAIL: - Timber.tag(TAG).d("MSG: %s", action); -// getPendingRequest(); - WCRequest wcRequest = (WCRequest) intent.getParcelableExtra("wcrequest"); - if (wcRequest != null) - { - executedPendingRequest(wcRequest.id); - receiveRequest(wcRequest); - } - else - { - // something went wrong - } - break; - case C.WALLET_CONNECT_CLIENT_TERMINATE: - String sessionId = intent.getStringExtra("sessionid"); - Timber.tag(TAG).d("MSG: TERMINATE: %s", sessionId); - if (viewModel != null) - { - viewModel.endSession(sessionId); - } - if (getSessionId() != null && getSessionId().equals(sessionId)) - { - setupClient(sessionId); - finish(); - } - break; - case C.WALLET_CONNECT_SWITCH_CHAIN: - Timber.tag(TAG).d("MSG: SWITCH CHAIN: "); - onSwitchChainRequest(intent); - break; - case C.WALLET_CONNECT_ADD_CHAIN: - Timber.tag(TAG).d("MSG: ADD CHAIN"); - onAddChainRequest(intent); - break; - } - } - }; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_wallet_connect); - - toolbar(); - - setTitle(getString(R.string.title_wallet_connect)); - - initViews(); - - initViewModel(); - - Timber.tag(TAG).d("Starting Activity: %s", getSessionId()); - - retrieveQrCode(); - viewModel.prepare(); - - if (savedInstanceState != null) restoreState(savedInstanceState); - } - - private void restoreState(Bundle savedInstance) - { - //Orientation change? - - if (savedInstance.containsKey("ORIENTATION") && savedInstance.containsKey("SESSIONID")) - { - int oldOrientation = savedInstance.getInt("ORIENTATION"); - int newOrientation = getResources().getConfiguration().orientation; - - if (oldOrientation != newOrientation) - { - requestId = savedInstance.getLong("SESSIONID"); - String sessionId = savedInstance.getString("SESSIONIDSTR"); - session = viewModel.getSession(sessionId); - - if (savedInstance.containsKey("TRANSACTION")) - { - Web3Transaction w3Tx = savedInstance.getParcelable("TRANSACTION"); - chainIdOverride = savedInstance.getLong("CHAINID"); - - //kick off transaction - final ActionSheetDialog confDialog = generateTransactionRequest(w3Tx, chainIdOverride); - if (confDialog != null) - { - confirmationDialog = confDialog; - confirmationDialog.show(); - } - } - else if (savedInstance.containsKey("SIGNDATA")) - { - signData = savedInstance.getString("SIGNDATA"); - signType = WCEthereumSignMessage.WCSignType.values()[savedInstance.getInt("SIGNTYPE")]; - lastId = savedInstance.getLong("LASTID"); - String peerUrl = savedInstance.getString("PEERURL"); - Signable signable = null; - - //kick off sign - switch (signType) - { - case MESSAGE: - case PERSONAL_MESSAGE: - signable = new EthereumMessage(signData, peerUrl, lastId, SignMessageType.SIGN_PERSONAL_MESSAGE); - break; - case TYPED_MESSAGE: - signable = new EthereumTypedMessage(signData, peerUrl, lastId, new CryptoFunctions()); - break; - } - - doSignMessage(signable); - } - } - } - } - - @Override - protected void onNewIntent(Intent intent) - { - super.onNewIntent(intent); - Bundle data = intent.getExtras(); - chainIdOverride = 0; - if (data != null) - { - //detect new intent from service - String sessionIdFromService = data.getString("session"); - if (sessionIdFromService != null) - { - handleStartFromWCMessage(sessionIdFromService); - } - else - { - handleStartDirectFromQRScan(); - } - } - } - - private void onServiceReady(Boolean b) - { - if (waitForWalletConnectSession) - { - waitForWalletConnectSession = false; - handleStartDirectFromQRScan(); - } - } - - private void handleStartDirectFromQRScan() - { - String sessionId = getSessionId(); - retrieveQrCode(); - String newSessionId = getSessionId(); - Timber.tag(TAG).d("Received New Intent: %s (%s)", newSessionId, sessionId); - viewModel.getClient(this, newSessionId, client -> { - if (client == null || !client.isConnected()) - { - //TODO: ERROR! - showErrorDialogTerminate(getString(R.string.session_terminated)); - infoLayout.setVisibility(View.GONE); - } - else - { - //setup the screen - Timber.tag(TAG).d("Resume Connection session: %s", newSessionId); - setClient(client); - } - }); - } - - private void handleStartFromWCMessage(String sessionIdFromService) - { - //is different from current sessionId? - if (!sessionIdFromService.equals(getSessionId())) - { - //restore different session - session = viewModel.getSession(sessionIdFromService); - viewModel.getClient(this, sessionIdFromService, this::setClient); - qrCode = null; - fromDappBrowser = false; - } - else - { - //init UI - displaySessionStatus(sessionIdFromService); - } - } - - private void setClient(WCClient receivedClient) - { - client = receivedClient; - displaySessionStatus(client.sessionId()); - } - - private void retrieveQrCode() - { - Bundle data = getIntent().getExtras(); - if (data != null) - { - String qrCode = data.getString("qrCode"); - String sessionId = data.getString("session"); - chainIdOverride = data.getLong(C.EXTRA_CHAIN_ID, 0); - if (sessionId != null) fromSessionActivity = true; - if (!TextUtils.isEmpty(qrCode)) - { - parseSessionCode(data.getString("qrCode")); - } - else if (!TextUtils.isEmpty(sessionId)) - { - session = viewModel.getSession(sessionId); - } - } - else - { - Toast.makeText(this, "Error retrieving QR code", Toast.LENGTH_SHORT).show(); - finish(); - } - } - - private void parseSessionCode(String wcCode) - { - if (wcCode != null && wcCode.startsWith(WC_LOCAL_PREFIX)) - { - wcCode = wcCode.replace(WC_LOCAL_PREFIX, ""); - fromDappBrowser = true; - } - else if (wcCode != null && wcCode.startsWith(WC_INTENT)) - { - wcCode = wcCode.replace(WC_INTENT, ""); - fromPhoneBrowser = true; //don't use this yet, but could use it for switching between apps - } - this.qrCode = wcCode; - session = WCSession.Companion.from(qrCode); - Timber.d("WCClient: %s", qrCode); - } - - private void initViewModel() - { - viewModel = new ViewModelProvider(this) - .get(WalletConnectViewModel.class); - - viewModel.defaultWallet().observe(this, this::onDefaultWallet); - viewModel.serviceReady().observe(this, this::onServiceReady); - viewModel.transactionFinalised().observe(this, this::txWritten); - viewModel.transactionError().observe(this, this::txError); - viewModel.transactionSigned().observe(this, this::txSigned); - - viewModel.startService(this); - } - - private void initViews() - { - progressBar = findViewById(R.id.progress); - infoLayout = findViewById(R.id.layout_info); - icon = findViewById(R.id.icon); - peerName = findViewById(R.id.peer_name); - peerUrl = findViewById(R.id.peer_url); - statusText = findViewById(R.id.connection_status); - textName = findViewById(R.id.text_name); - txCountLayout = findViewById(R.id.layout_tx_count); - txCount = findViewById(R.id.tx_count); - chainName = findViewById(R.id.chain_name); - chainIcon = findViewById(R.id.chain_icon); - - progressBar.setVisibility(View.VISIBLE); - infoLayout.setVisibility(View.GONE); - - functionBar = findViewById(R.id.layoutButtons); - functionBar.setupFunctions(this, new ArrayList<>(Collections.singletonList(R.string.action_end_session))); - functionBar.setVisibility(View.GONE); - } - - @Override - public void handleClick(String action, int id) - { - if (id == R.string.action_end_session) - { - endSessionDialog(); - } - } - - //TODO: Refactor this into elements - this function is unmaintainable - private void onDefaultWallet(Wallet wallet) - { - Timber.tag(TAG).d("Open Connection: %s", getSessionId()); - - String peerId; - String sessionId = getSessionId(); - String connectionId = viewModel.getRemotePeerId(sessionId); - if (!TextUtils.isEmpty(viewModel.getWallet().address)) - { - if (connectionId == null && session != null) //new session request - { - Timber.tag(TAG).d("New Session: %s", getSessionId()); - //new connection, create a random ID to identify us to the remotePeer. - peerId = UUID.randomUUID().toString(); //Create a new ID for our side of this session. The remote peer uses this ID to identify us - connectionId = null; //connectionId is only relevant for resuming a session - } - else //existing session, rebuild the session data - { - //try to retrieve the session from database - session = viewModel.getSession(sessionId); - displaySessionStatus(sessionId); - - viewModel.getClient(this, sessionId, client -> { - Timber.tag(TAG).d("Resume Session: %s", getSessionId()); - - if (client == null && fromSessionActivity) - { - functionBar.setVisibility(View.GONE); - return; - } - else if (client == null || !client.isConnected()) - { - if (client == null || (!fromSessionActivity && session == null)) - { - showErrorDialogTerminate(getString(R.string.session_terminated)); - infoLayout.setVisibility(View.GONE); - } - else - { - //attempt to restart the session; this will allow session 'force' close - restartSession(session); - } - } - else - { -// getPendingRequest(); - setClient(client); - } - }); - - return; - } - - Timber.tag(TAG).d("connect: peerID %s", peerId); - Timber.tag(TAG).d("connect: remotePeerID %s", connectionId); - - initWalletConnectPeerMeta(); - initWalletConnectClient(); - initWalletConnectSession(peerId, connectionId); - } - } - - private void restartSession(WCSession session) - { - String sessionId = session.getTopic(); - client = WCUtils.createWalletConnectSession(this, viewModel.getWallet(), - session, viewModel.getPeerId(sessionId), viewModel.getRemotePeerId(sessionId)); - viewModel.putClient(this, sessionId, client); - } - - @SuppressWarnings("MethodOnlyUsedFromInnerClass") - private void executedPendingRequest(long id) - { - viewModel.removePendingRequest(this, id); - } - - @Override - public boolean receiveRequest(WCRequest rq) - { - if (rq != null) - { - requestId = rq.id; - long useChainId = viewModel.getChainId(getSessionId()); - switch (rq.type) - { - case MESSAGE: - if (watchOnly(rq.id)) return false; - runOnUiThread(() -> { - onEthSign(rq.id, rq.sign, useChainId); - }); - break; - case SIGN_TX: - if (watchOnly(rq.id)) return false; - runOnUiThread(() -> { - onEthSignTransaction(rq.id, rq.tx, useChainId); - }); - break; - case SEND_TX: - if (watchOnly(rq.id)) return false; - runOnUiThread(() -> { - onEthSendTransaction(rq.id, rq.tx, useChainId); - }); - break; - case FAILURE: - runOnUiThread(() -> { - onFailure(rq.throwable); - }); - break; - case SESSION_REQUEST: - Timber.tag(TAG).d("On Request: %s", rq.peer.getName()); - runOnUiThread(() -> { - onSessionRequest(rq.id, rq.peer, rq.chainId); - }); - break; - } - } - - return true; - } - - private boolean watchOnly(long id) - { - if (!viewModel.getWallet().canSign()) - { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.watch_wallet); - dialog.setButton(R.string.action_close, v -> { - viewModel.rejectRequest(getApplication(), getSessionId(), - id, getString(R.string.message_authentication_failed)); - dialog.dismiss(); - }); - dialog.setCancelable(false); - dialog.show(); - return true; - } - else - { - return false; - } - } - - private void closeErrorDialog() - { - if (dialog != null && dialog.isShowing()) - { - dialog.dismiss(); - } - } - - private void startMessageCheck() - { - IntentFilter filter = new IntentFilter(C.WALLET_CONNECT_REQUEST); - filter.addAction(C.WALLET_CONNECT_NEW_SESSION); - filter.addAction(C.WALLET_CONNECT_FAIL); - filter.addAction(C.WALLET_CONNECT_CLIENT_TERMINATE); - filter.addAction(C.WALLET_CONNECT_SWITCH_CHAIN); - filter.addAction(C.WALLET_CONNECT_ADD_CHAIN); - if (broadcastManager == null) broadcastManager = LocalBroadcastManager.getInstance(this); - broadcastManager.registerReceiver(walletConnectActionReceiver, filter); - } - - private String getSessionId() - { - if (qrCode != null) - { - String uriString = qrCode.replace("wc:", "wc://"); - return Uri.parse(uriString).getUserInfo(); - } - else if (session != null) - { - return session.getTopic(); - } - else - { - return null; - } - } - - private void initWalletConnectSession(String peerId, String connectionId) - { - if (session == null) - { - //error situation! - invalidSession(); - return; - } - - Timber.tag(TAG).d("Connect: %s (%s)", getSessionId(), connectionId); - client.connect(session, peerMeta, peerId, connectionId); - - client.setOnFailure(throwable -> { - Timber.tag(TAG).d("On Fail: %s", throwable.getMessage()); - showErrorDialog("Error: " + throwable.getMessage()); - return Unit.INSTANCE; - }); - - handler.postDelayed(() -> { - //Timeout check - if (client != null && client.chainIdVal() == 0 && (dialog == null || !dialog.isShowing())) - { - //show timeout - showTimeoutDialog(); - } - }, CONNECT_TIMEOUT); - } - - private void invalidSession() - { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.invalid_walletconnect_session); - dialog.setMessage(R.string.restart_walletconnect_session); - dialog.setButton(R.string.dialog_ok, v -> { - dialog.dismiss(); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, getString(R.string.invalid_walletconnect_session)); - } - - private void initWalletConnectPeerMeta() - { - peerMeta = new WCPeerMeta( - getString(R.string.app_name), - C.ALPHAWALLET_WEB, - viewModel.getWallet().address, - new ArrayList<>(Collections.singleton(C.ALPHAWALLET_LOGO_URI)) - ); - } - - private void initWalletConnectClient() - { - client = new WCClient(); - - client.setOnWCOpen(peerId -> { - viewModel.putClient(this, getSessionId(), client); - Timber.tag(TAG).d("On Open: %s", peerId); - return Unit.INSTANCE; - }); - } - - @Override - protected void onSaveInstanceState(@NotNull Bundle state) - { - super.onSaveInstanceState(state); - //need to preserve the orientation and current signing request - state.putInt("ORIENTATION", getResources().getConfiguration().orientation); - state.putLong("SESSIONID", requestId); - state.putString("SESSIONIDSTR", getSessionId()); - if (confirmationDialog != null && confirmationDialog.isShowing() && confirmationDialog.getTransaction() != null) - { - state.putParcelable("TRANSACTION", confirmationDialog.getTransaction()); - state.putLong("CHAINID", viewModel.getChainId(getSessionId())); - } - if (confirmationDialog != null && confirmationDialog.isShowing() && signData != null) - { - state.putString("SIGNDATA", signData); - state.putInt("SIGNTYPE", signType.ordinal()); - state.putLong("LASTID", lastId); - state.putString("PEERURL", peerUrl.getText().toString()); - } - } - - private void setupClient(final String sessionId) - { - viewModel.getClient(this, sessionId, client -> - runOnUiThread(() -> { - if (client == null || !client.isConnected()) - { - statusText.setText(R.string.not_connected); - statusText.setTextColor(getColor(R.color.error)); - } - else - { - statusText.setText(R.string.online); - statusText.setTextColor(getColor(R.color.positive)); - } - })); - } - - private void displaySessionStatus(String sessionId) - { - progressBar.setVisibility(View.GONE); - functionBar.setVisibility(View.VISIBLE); - infoLayout.setVisibility(View.VISIBLE); - WCPeerMeta remotePeerData = viewModel.getRemotePeer(sessionId); - this.remotePeerMeta = remotePeerData; // init meta to access in other places - if (remotePeerData != null) - { - if (remotePeerData.getIcons().isEmpty()) - { - icon.setImageResource(R.drawable.ic_coin_eth_small); - } - else - { - Glide.with(this) - .load(remotePeerData.getIcons().get(0)) - .circleCrop() - .into(icon); - } - peerName.setText(remotePeerData.getName()); - textName.setText(remotePeerData.getName()); - peerUrl.setText(remotePeerData.getUrl()); - chainName.setChainID(viewModel.getChainId(sessionId)); - chainIcon.setVisibility(View.VISIBLE); - chainIcon.bindData(viewModel.getChainId(sessionId)); - viewModel.startGasCycle(viewModel.getChainId(sessionId)); - updateSignCount(); - } - } - - private void onSessionRequest(Long id, WCPeerMeta peer, long chainId) - { - if (peer == null) - { - finish(); - } - - closeErrorDialog(); - - closeConfirmationDialog(); - - String displayIcon = (peer.getIcons().size() > 0) ? peer.getIcons().get(0) : DEFAULT_ICON; - - chainIdOverride = chainIdOverride > 0 ? chainIdOverride : (chainId > 0 ? chainId : MAINNET_ID); - - displaySessionRequestDetails(peer, chainIdOverride, displayIcon); - - confirmationDialog = new ActionSheetDialog(this, peer, chainIdOverride, displayIcon, this); - if (confirmationDialog.getActionSheetStatus() == ActionSheetStatus.ERROR_INVALID_CHAIN) - { - showErrorDialogUnsupportedNetwork(id, chainIdOverride); - return; - } - else - { - confirmationDialog.show(); - confirmationDialog.fullExpand(); - viewModel.track(Analytics.Action.WALLET_CONNECT_SESSION_REQUEST); - } - - if (confirmationDialog.isShowing() && - !viewModel.isActiveNetwork(chainId) && - !viewModel.isActiveNetwork(chainIdOverride)) - { - openChainSelection(); - } - } - - private void displaySessionRequestDetails(WCPeerMeta peer, long chainId, String displayIcon) - { - Glide.with(this) - .load(displayIcon) - .circleCrop() - .into(icon); - peerName.setText(peer.getName()); - textName.setText(peer.getName()); - peerUrl.setText(peer.getUrl()); - txCount.setText(R.string.empty); - chainName.setChainID(chainId); - chainIcon.setVisibility(View.VISIBLE); - chainIcon.bindData(chainId); - remotePeerMeta = peer; - } - - private void closeConfirmationDialog() - { - if (confirmationDialog != null) - { - if (confirmationDialog.isShowing()) - { // if already opened - confirmationDialog.forceDismiss(); - } - } - } - - private void onEthSign(Long id, WCEthereumSignMessage message, long chainId) - { - Signable signable = null; - lastId = id; - signData = message.getData(); - signType = message.getType(); - - switch (message.getType()) - { - case MESSAGE: - // see https://docs.walletconnect.org/json-rpc-api-methods/ethereum - // WalletConnect doesn't provide access to deprecated eth_sign - // Instead it uses sign_personal for both - // signable = new EthereumMessage(message.getData(), peerUrl.getText().toString(), id, SignMessageType.SIGN_MESSAGE); - // break; - // Drop through - case PERSONAL_MESSAGE: - signable = new EthereumMessage(message.getData(), peerUrl.getText().toString(), id, SignMessageType.SIGN_PERSONAL_MESSAGE); - doSignMessage(signable); - break; - case TYPED_MESSAGE: - signable = new EthereumTypedMessage(message.getData(), peerUrl.getText().toString(), id, new CryptoFunctions()); - if (signable.getChainId() != chainId) - { - showErrorDialogIncompatibleNetwork(signable.getCallbackId(), signable.getChainId(), chainId); - } - else - { - doSignMessage(signable); - } - break; - } - } - - private void showErrorDialogUnsupportedNetwork(long callbackId, long chainId) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - String message = getString(R.string.error_walletconnect_session_request_unsupported_network, String.valueOf(chainId)); - dialog.setMessage(message); - dialog.setButton(R.string.action_close, v -> { - dialog.dismiss(); - dismissed("", callbackId, false); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - private void showErrorDialogIncompatibleNetwork(long callbackId, long requestingChainId, long activeChainId) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - String message = EthereumNetworkBase.isChainSupported(requestingChainId) ? - getString(R.string.error_eip712_incompatible_network, - EthereumNetworkBase.getShortChainName(requestingChainId), - EthereumNetworkBase.getShortChainName(activeChainId)) : - getString(R.string.error_eip712_unsupported_network, String.valueOf(requestingChainId)); - dialog.setMessage(message); - dialog.setButton(R.string.action_cancel, v -> { - dialog.dismiss(); - dismissed("", callbackId, false); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - private void onFailure(@NonNull Throwable throwable) - { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.title_dialog_error); - dialog.setMessage(throwable.getMessage()); - dialog.setButton(R.string.try_again, v -> onDefaultWallet(viewModel.getWallet())); - dialog.setSecondaryButton(R.string.action_cancel, v -> { - dialog.dismiss(); - killSession(); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - AnalyticsProperties props = new AnalyticsProperties(); - props.put(Analytics.PROPS_ERROR_MESSAGE, throwable.getMessage()); - viewModel.track(Analytics.Action.WALLET_CONNECT_TRANSACTION_FAILED, props); - } - - private void doSignMessage(final Signable signable) - { - confirmationDialog = new ActionSheetSignDialog(this, this, signable); - confirmationDialog.show(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_SIGN_MESSAGE_REQUEST); - } - - @Override - public void signingComplete(SignatureFromKey signature, Signable signable) - { - viewModel.recordSign(signable, getSessionId(), () -> { - viewModel.approveRequest(getApplication(), getSessionId(), signable.getCallbackId(), Numeric.toHexString(signature.signature)); - confirmationDialog.success(); - if (fromDappBrowser) - { - confirmationDialog.forceDismiss(); - switchToDappBrowser(); - } - requestId = 0; - lastId = 0; - signData = null; - updateSignCount(); - }); - } - - @Override - public void signingFailed(Throwable error, Signable message) - { - showErrorDialog(error.getMessage()); - confirmationDialog.dismiss(); - if (fromDappBrowser) switchToDappBrowser(); - requestId = 0; - lastId = 0; - signData = null; - } - - @Override - public WalletType getWalletType() - { - return viewModel.getWallet().type; - } - - private void onEthSignTransaction(Long id, WCEthereumTransaction transaction, long chainId) - { - lastId = id; - final Web3Transaction w3Tx = new Web3Transaction(transaction, id, SignType.SIGN_TX); - final ActionSheetDialog confDialog = generateTransactionRequest(w3Tx, chainId); - if (confDialog != null) - { - confirmationDialog = confDialog; - confirmationDialog.setSignOnly(); //sign transaction only - confirmationDialog.show(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_SIGN_TRANSACTION_REQUEST); - } - } - - private void onEthSendTransaction(Long id, WCEthereumTransaction transaction, long chainId) - { - lastId = id; - final Web3Transaction w3Tx = new Web3Transaction(transaction, id, SignType.SEND_TX); - final ActionSheetDialog confDialog = generateTransactionRequest(w3Tx, chainId); - if (confDialog != null) - { - confirmationDialog = confDialog; - confirmationDialog.show(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_SEND_TRANSACTION_REQUEST); - } - } - - private ActionSheetDialog generateTransactionRequest(Web3Transaction w3Tx, long chainId) - { - try - { - if ((confirmationDialog == null || !confirmationDialog.isShowing()) && - (w3Tx.recipient.equals(Address.EMPTY) && w3Tx.payload != null) // Constructor - || (!w3Tx.recipient.equals(Address.EMPTY) && (w3Tx.payload != null || w3Tx.value != null))) // Raw or Function TX - { - WCPeerMeta remotePeerData = viewModel.getRemotePeer(getSessionId()); - Token token = viewModel.getTokensService().getTokenOrBase(chainId, w3Tx.recipient.toString()); - final ActionSheetDialog confDialog = new ActionSheetDialog(this, w3Tx, token, "", - w3Tx.recipient.toString(), viewModel.getTokensService(), this); - confDialog.setURL(remotePeerData.getUrl()); - confDialog.setCanceledOnTouchOutside(false); - confDialog.waitForEstimate(); - - viewModel.calculateGasEstimate(viewModel.getWallet(), w3Tx, chainId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(confDialog::setGasEstimate, - Throwable::printStackTrace) - .isDisposed(); - - return confDialog; - } - } - catch (Exception e) - { - Timber.e(e); - } - - return null; - } - - private void killSession() - { - Timber.tag(TAG).d(": Terminate Session: %s", getSessionId()); - if (client != null && session != null && client.isConnected()) - { - viewModel.track(Analytics.Action.WALLET_CONNECT_SESSION_ENDED); - client.killSession(); - viewModel.disconnectSession(this, client.sessionId()); - awWalletConnectClient.updateNotification(); - handler.postDelayed(this::finish, 5000); - } - else - { - finish(); - } - } - - @Override - public void onPause() - { - super.onPause(); - broadcastManager.unregisterReceiver(walletConnectActionReceiver); - } - - @Override - public void onResume() - { - super.onResume(); - viewModel.track(Analytics.Navigation.WALLET_CONNECT_SESSION_DETAIL); - //see if the session is active - setupClient(getSessionId()); - startMessageCheck(); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - if (viewModel != null) viewModel.onDestroy(); - } - - private void showTimeoutDialog() - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.title_dialog_error); - dialog.setMessage(R.string.walletconnect_timeout); - dialog.setButton(R.string.ok, v -> dialog.dismiss()); - dialog.setSecondaryButton(R.string.action_close, v -> { - dialog.dismiss(); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_CONNECTION_TIMEOUT); - }); - } - } - - private void showErrorDialog(String message) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.title_dialog_error); - dialog.setMessage(message); - dialog.setButton(R.string.try_again, v -> onDefaultWallet(viewModel.getWallet())); - dialog.setSecondaryButton(R.string.action_close, v -> { - dialog.dismiss(); - killSession(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - private void showErrorDialogCancel(String title, String message) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(title); - dialog.setMessage(message); - dialog.setButton(R.string.action_cancel, v -> dialog.dismiss()); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - @Override - public void onBackPressed() - { - //TODO: If from phone browser then we should return a code that tells main activity to finish - if (fromDappBrowser) - { - switchToDappBrowser(); - } - else - { - //hand back to phone browser - finish(); - } - } - - @Override - public void finish() - { - super.finish(); - } - - private void endSessionDialog() - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.dialog_title_disconnect_session); - dialog.setButton(R.string.action_close, v -> { - dialog.dismiss(); - killSession(); - }); - dialog.setSecondaryButton(R.string.action_cancel, v -> dialog.dismiss()); - dialog.setCancelable(false); - dialog.show(); - }); - } - - private void showErrorDialogTerminate(String message) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.title_dialog_error); - dialog.setMessage(message); - dialog.setButton(R.string.dialog_ok, v -> { - dialog.dismiss(); - finish(); - }); - dialog.setCancelable(false); - dialog.show(); - - viewModel.trackError(Analytics.Error.WALLET_CONNECT, message); - }); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) - { - if (item.getItemId() == android.R.id.home) - { - onBackPressed(); - } - return false; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) - { - super.onActivityResult(requestCode, resultCode, data); - - if (requestCode >= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS && requestCode <= SignTransactionDialog.REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS + 10) - { - if (confirmationDialog != null && confirmationDialog.isShowing()) - confirmationDialog.completeSignRequest(resultCode == RESULT_OK); - } - if (resultCode == RESULT_OK) - { - if (requestCode == C.REQUEST_TRANSACTION_CALLBACK) - { - handleTransactionCallback(resultCode, data); - } - } - else - { - showErrorDialogCancel(peerName.getText().toString(), getString(R.string.message_transaction_not_sent)); - if (requestCode == C.REQUEST_TRANSACTION_CALLBACK) - { - viewModel.rejectRequest(this, getSessionId(), lastId, getString(R.string.message_authentication_failed)); - } - else - { - viewModel.rejectRequest(this, getSessionId(), lastId, getString(R.string.message_reject_request)); - } - } - } - - //return from the openConfirmation above - public void handleTransactionCallback(int resultCode, Intent data) - { - if (data == null) return; - final Web3Transaction web3Tx = data.getParcelableExtra(C.EXTRA_WEB3TRANSACTION); - if (resultCode == RESULT_OK && web3Tx != null) - { - String hashData = data.getStringExtra(C.EXTRA_TRANSACTION_DATA); - viewModel.recordSignTransaction(getApplicationContext(), web3Tx, String.valueOf(viewModel.getChainId(getSessionId())), getSessionId()); - viewModel.approveRequest(this, getSessionId(), lastId, hashData); - } - else if (web3Tx != null) - { - showErrorDialogCancel(getString(R.string.title_wallet_connect), getString(R.string.message_transaction_not_sent)); - viewModel.rejectRequest(this, getSessionId(), lastId, getString(R.string.message_reject_request)); - } - - if (fromDappBrowser) - { - if (confirmationDialog != null) confirmationDialog.forceDismiss(); - switchToDappBrowser(); - } - } - - private void switchToDappBrowser() - { - Intent intent = new Intent(this, HomeActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - startActivity(intent); - } - - @Override - public void getAuthorisation(SignAuthenticationCallback callback) - { - viewModel.getAuthenticationForSignature(viewModel.getWallet(), this, callback); - } - - @Override - public void sendTransaction(Web3Transaction finalTx) - { - viewModel.requestSignature(finalTx, viewModel.getWallet(), viewModel.getChainId(getSessionId())); - } - - /** - * Complete Hardware sendTx/signTx - */ - @Override - public void completeSendTransaction(Web3Transaction tx, SignatureFromKey signature) - { - viewModel.sendTransaction(viewModel.getWallet(), viewModel.getChainId(getSessionId()), tx, signature); - } - - @Override - public void completeSignTransaction(Web3Transaction w3Tx, SignatureFromKey signature) - { - viewModel.signTransaction(viewModel.getChainId(getSessionId()), w3Tx, signature); - } - - public void txWritten(TransactionReturn txReturn) - { - viewModel.recordSignTransaction(getApplicationContext(), txReturn.tx, String.valueOf(viewModel.getChainId(getSessionId())), getSessionId()); - viewModel.approveRequest(getApplication(), getSessionId(), txReturn.tx.leafPosition, txReturn.hash); - confirmationDialog.transactionWritten(getString(R.string.dialog_title_sign_transaction)); - if (fromDappBrowser) switchToDappBrowser(); - confirmationDialog.transactionWritten(txReturn.hash); - requestId = 0; - updateSignCount(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_TRANSACTION_SUCCESS); - } - - private void txSigned(TransactionReturn sigData) - { - if (sigData != null) - { - viewModel.recordSignTransaction(getApplicationContext(), sigData.tx, String.valueOf(viewModel.getChainId(getSessionId())), getSessionId()); - viewModel.approveRequest(getApplication(), getSessionId(), sigData.tx.leafPosition, sigData.hash); - confirmationDialog.transactionWritten(getString(R.string.dialog_title_sign_transaction)); - if (fromDappBrowser) - { - switchToDappBrowser(); - } - requestId = 0; - updateSignCount(); - } - } - - //Transaction failed to be sent - private void txError(TransactionReturn txError) - { - if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) - { - runOnUiThread(() -> { - closeErrorDialog(); - dialog = new AWalletAlertDialog(this, AWalletAlertDialog.ERROR); - dialog.setTitle(R.string.invalid_walletconnect_session); - dialog.setTitle(R.string.error_transaction_failed); - dialog.setMessage(txError.throwable.getMessage()); - dialog.setButtonText(R.string.button_ok); - dialog.setButtonListener(v -> { - dialog.dismiss(); - if (fromDappBrowser) switchToDappBrowser(); - }); - dialog.show(); - - AnalyticsProperties props = new AnalyticsProperties(); - props.put(Analytics.PROPS_ERROR_MESSAGE, txError.throwable.getMessage()); - viewModel.track(Analytics.Action.WALLET_CONNECT_TRANSACTION_FAILED, props); - }); - } - //TODO: Report error fail to WalletConnect - } - - @Override - public void dismissed(String txHash, long callbackId, boolean actionCompleted) - { - //actionsheet dismissed - if action not completed then user cancelled - if (!actionCompleted) - { - viewModel.rejectRequest(this, getSessionId(), callbackId, getString(R.string.message_reject_request)); - viewModel.track(Analytics.Action.WALLET_CONNECT_TRANSACTION_CANCELLED); - } - - if (fromDappBrowser) switchToDappBrowser(); - requestId = 0; - confirmationDialog = null; - } - - @Override - public void notifyConfirm(String mode) - { - AnalyticsProperties props = new AnalyticsProperties(); - props.put(Analytics.PROPS_ACTION_SHEET_MODE, mode); - props.put(Analytics.PROPS_ACTION_SHEET_SOURCE, ActionSheetSource.WALLET_CONNECT.getValue()); - viewModel.track(Analytics.Action.ACTION_SHEET_COMPLETED, props); - } - - @Override - public ActivityResultLauncher gasSelectLauncher() - { - return getGasSettings; - } - - @Override - public void signTransaction(Web3Transaction tx) - { - viewModel.requestSignatureOnly(tx, viewModel.getWallet(), viewModel.getChainId(getSessionId())); - } - - @Override - public void notifyWalletConnectApproval(long selectedChain) - { - client.approveSession(Collections.singletonList(viewModel.getWallet().address), selectedChain); - //update client in service - viewModel.putClient(WalletConnectActivity.this, getSessionId(), client); - viewModel.createNewSession(getSessionId(), client.getPeerId(), client.getRemotePeerId(), - new Gson().toJson(session), new Gson().toJson(remotePeerMeta), selectedChain); - chainName.setChainID(selectedChain); - chainIcon.setVisibility(View.VISIBLE); - chainIcon.bindData(selectedChain); - progressBar.setVisibility(View.GONE); - functionBar.setVisibility(View.VISIBLE); - infoLayout.setVisibility(View.VISIBLE); - chainIdOverride = selectedChain; - setupClient(getSessionId()); //should populate this activity - viewModel.track(Analytics.Action.WALLET_CONNECT_SESSION_APPROVED); - if (fromDappBrowser) - { - //switch back to dappBrowser - switchToDappBrowser(); - } - } - - @Override - public GasService getGasService() - { - return viewModel.getGasService(); - } - - @Override - public void denyWalletConnect() - { - client.rejectSession(getString(R.string.message_reject_request)); - viewModel.track(Analytics.Action.WALLET_CONNECT_SESSION_REJECTED); - finish(); - } - - @Override - public void openChainSelection() - { - ActionSheetCallback.super.openChainSelection(); - Intent intent = new Intent(WalletConnectActivity.this, NetworkChooserActivity.class); - intent.putExtra(C.EXTRA_SINGLE_ITEM, true); - intent.putExtra(C.EXTRA_CHAIN_ID, chainIdOverride); - getNetwork.launch(intent); - } - - private void showSwitchChainDialog() - { - try - { - Token baseToken = viewModel.getTokenService().getTokenOrBase(switchChainId, viewModel.defaultWallet().getValue().address); - NetworkInfo newNetwork = EthereumNetworkBase.getNetworkInfo(switchChainId); - NetworkInfo activeNetwork = EthereumNetworkBase.getNetworkInfo(client.chainIdVal()); - if (confirmationDialog != null && confirmationDialog.isShowing()) - return; - confirmationDialog = new ActionSheetDialog(this, this, R.string.switch_chain_request, R.string.switch_and_reload, - switchChainDialogCallbackId, baseToken, activeNetwork, newNetwork); - confirmationDialog.setOnDismissListener(dialog -> { - viewModel.approveSwitchEthChain(WalletConnectActivity.this, switchChainRequestId, currentSessionId, switchChainId, false, chainAvailable); - confirmationDialog.setOnDismissListener(null); // remove from here as dialog is multi-purpose - confirmationDialog = null; - }); - confirmationDialog.setCanceledOnTouchOutside(false); - confirmationDialog.show(); - confirmationDialog.fullExpand(); - - viewModel.track(Analytics.Action.WALLET_CONNECT_SWITCH_NETWORK_REQUEST); - } - catch (Exception e) - { - Timber.e(e); - } - } - - private void onSwitchChainRequest(Intent intent) - { - name = intent.getStringExtra(C.EXTRA_NAME); - switchChainRequestId = intent.getLongExtra(C.EXTRA_WC_REQUEST_ID, -1); - switchChainId = intent.getLongExtra(C.EXTRA_CHAIN_ID, -1); - currentSessionId = intent.getStringExtra(C.EXTRA_SESSION_ID); - Timber.tag(TAG).d("MSG: SWITCH CHAIN: name: %s, chainId: %s", name, switchChainId); - - if (currentSessionId == null || !session.getTopic().equals(currentSessionId)) - { - Timber.tag(TAG).d("Wrong session"); - return; - } - if (switchChainId == -1 || requestId == -1) - { - Timber.tag(TAG).d("Cant find data"); - return; - } - chainAvailable = EthereumNetworkBase.getNetworkInfo(switchChainId) != null; - // reject with error message as the chain is not added - if (!chainAvailable) - { - viewModel.approveSwitchEthChain(WalletConnectActivity.this, requestId, currentSessionId, switchChainId, false, false); - } - else - { - showSwitchChainDialog(); - } - } - - private void onAddChainRequest(Intent intent) - { - long requestId = intent.getLongExtra(C.EXTRA_WC_REQUEST_ID, -1); - String currentSessionId = intent.getStringExtra(C.EXTRA_SESSION_ID); - WalletAddEthereumChainObject chainObject = intent.getParcelableExtra(C.EXTRA_CHAIN_OBJ); - if (chainObject != null) - { - // showing dialog because chain is not added - addEthereumChainPrompt = new AddEthereumChainPrompt( - this, - chainObject, - chainObject1 -> { - this.addEthereumChainPrompt.setOnDismissListener(null); - this.addEthereumChainPrompt.dismiss(); - viewModel.approveAddEthereumChain( - WalletConnectActivity.this, - requestId, - currentSessionId, - chainObject, - true - ); - viewModel.updateSession(currentSessionId, chainObject.getChainId()); - displaySessionStatus(currentSessionId); - } - ); - - addEthereumChainPrompt.setOnDismissListener(dialog -> { - viewModel.approveAddEthereumChain( - WalletConnectActivity.this, - requestId, - currentSessionId, - chainObject, - false - ); - }); - addEthereumChainPrompt.show(); - } - else - { - viewModel.approveAddEthereumChain( - WalletConnectActivity.this, - requestId, - currentSessionId, - chainObject, - false - ); - } - } - - @Override - public void buttonClick(long callbackId, Token baseToken) - { - if (callbackId == switchChainDialogCallbackId && confirmationDialog != null) - { - confirmationDialog.setOnDismissListener(null); - confirmationDialog.dismiss(); - viewModel.approveSwitchEthChain(WalletConnectActivity.this, switchChainRequestId, currentSessionId, switchChainId, true, chainAvailable); - viewModel.updateSession(currentSessionId, switchChainId); - displaySessionStatus(session.getTopic()); - } - } - - private void updateSignCount() - { - ArrayList recordList = viewModel.getSignRecords(getSessionId()); - txCount.setText(String.valueOf(recordList.size())); - if (recordList.size() > 0) - { - txCountLayout.setOnClickListener(v -> { - Intent intent = new Intent(getApplication(), SignDetailActivity.class); - intent.putParcelableArrayListExtra(C.EXTRA_STATE, recordList); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - startActivity(intent); - }); - } - } -} diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java index f1bb6af005..62cb08d303 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletConnectSessionActivity.java @@ -352,17 +352,9 @@ class CustomViewHolder extends RecyclerView.ViewHolder public static Intent newIntent(Context context, WalletConnectSessionItem session) { - Intent intent; - if (session instanceof WalletConnectV2SessionItem) - { - intent = new Intent(context, WalletConnectV2Activity.class); - intent.putExtra("session", (WalletConnectV2SessionItem) session); - } - else - { - intent = new Intent(context, WalletConnectActivity.class); - intent.putExtra("session", session.sessionId); - } + Intent intent = new Intent(context, WalletConnectV2Activity.class); + intent.putExtra("session", (WalletConnectV2SessionItem) session); + intent.addFlags(FLAG_ACTIVITY_NEW_TASK); return intent; } diff --git a/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java b/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java index 100cd17668..c1aaf2d7c2 100644 --- a/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/WalletsActivity.java @@ -35,7 +35,6 @@ import com.alphawallet.app.repository.EthereumNetworkRepository; import com.alphawallet.app.repository.PreferenceRepositoryType; import com.alphawallet.app.service.KeyService; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.ui.widget.adapter.WalletsSummaryAdapter; import com.alphawallet.app.viewmodel.WalletsViewModel; import com.alphawallet.app.widget.AWalletAlertDialog; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java index 49f0598d36..6cc8585058 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/DappBrowserViewModel.java @@ -43,7 +43,6 @@ import com.alphawallet.app.ui.MyAddressActivity; import com.alphawallet.app.ui.QRScanning.QRScannerActivity; import com.alphawallet.app.ui.SendActivity; -import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.util.DappBrowserUtils; import com.alphawallet.app.walletconnect.util.WalletConnectHelper; @@ -366,16 +365,7 @@ public void stopBalanceUpdate() public void handleWalletConnect(Context context, String url, NetworkInfo activeNetwork) { - Intent intent; - if (WalletConnectHelper.isWalletConnectV1(url)) - { - intent = getIntentOfWalletConnectV1(context, url, activeNetwork); - } - else - { - intent = getIntentOfWalletConnectV2(context, url); - } - + Intent intent = getIntentOfWalletConnectV2(context, url); context.startActivity(intent); } @@ -387,17 +377,6 @@ private Intent getIntentOfWalletConnectV2(Context context, String url) return intent; } - @NonNull - private Intent getIntentOfWalletConnectV1(Context context, String url, NetworkInfo activeNetwork) - { - String importPassData = WalletConnectActivity.WC_LOCAL_PREFIX + url; - Intent intent = new Intent(context, WalletConnectActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); - intent.putExtra(C.EXTRA_CHAIN_ID, activeNetwork.chainId); - intent.putExtra("qrCode", importPassData); - return intent; - } - public TokensService getTokenService() { return tokensService; diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java index 607e1afced..ca502bdca7 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java @@ -60,12 +60,10 @@ import com.alphawallet.app.service.RealmManager; import com.alphawallet.app.service.TokensService; import com.alphawallet.app.service.TransactionsService; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.ui.AddTokenActivity; import com.alphawallet.app.ui.HomeActivity; import com.alphawallet.app.ui.ImportWalletActivity; import com.alphawallet.app.ui.SendActivity; -import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.WalletConnectV2Activity; import com.alphawallet.app.util.QRParser; import com.alphawallet.app.util.RateApp; @@ -73,7 +71,6 @@ import com.alphawallet.app.util.ens.AWEnsResolver; import com.alphawallet.app.walletconnect.WCClient; import com.alphawallet.app.walletconnect.entity.WCUtils; -import com.alphawallet.app.walletconnect.util.WalletConnectHelper; import com.alphawallet.app.widget.EmailPromptView; import com.alphawallet.app.widget.QRCodeActionsView; import com.alphawallet.app.widget.WhatsNewView; @@ -222,15 +219,12 @@ public LiveData defaultWallet() public GasService getGasService() { return gasService; } - public void prepare(Activity activity) + public void prepare() { progress.postValue(false); disposable = genericWalletInteract .find() - .subscribe(w -> { - onDefaultWallet(w); - initWalletConnectSessions(activity, w); - }, this::onError); + .subscribe(this::onDefaultWallet, this::onError); } public void onClean() @@ -462,21 +456,10 @@ public boolean requiresProcessing(String qrCode) private void startWalletConnect(Activity activity, String qrCode) { - Intent intent; - if (WalletConnectHelper.isWalletConnectV1(qrCode)) - { - intent = new Intent(activity, WalletConnectActivity.class); - intent.putExtra("qrCode", qrCode); - //intent.putExtra(C.EXTRA_CHAIN_ID, 0); - } - else - { - intent = new Intent(activity, WalletConnectV2Activity.class); - intent.putExtra("url", qrCode); - } + Intent intent = new Intent(activity, WalletConnectV2Activity.class); + intent.putExtra("url", qrCode); + activity.startActivity(intent); - //setResult(WALLET_CONNECT); - //finish(); } private void showActionSheet(Activity activity, QRResult qrResult) @@ -785,57 +768,6 @@ public void setCurrencyAndLocale(Context context) currencyRepository.setDefaultCurrency(preferenceRepository.getDefaultCurrency()); } - // Restart walletconnect sessions if required - private void initWalletConnectSessions(Activity activity, Wallet wallet) - { - List clientMap = new ArrayList<>(); - long cutOffTime = System.currentTimeMillis() - DateUtils.DAY_IN_MILLIS * 2; - try (Realm realm = realmManager.getRealmInstance(WC_SESSION_DB)) - { - RealmResults items = realm.where(RealmWCSession.class) - .greaterThan("lastUsageTime", cutOffTime) - .sort("lastUsageTime", Sort.DESCENDING) - .findAll(); - - for (RealmWCSession r : items) - { - String peerId = r.getPeerId(); - if (!TextUtils.isEmpty(peerId)) - { - // restart the session if it's not already known by the service - clientMap.add(WCUtils.createWalletConnectSession(activity, wallet, - r.getSession(), peerId, r.getRemotePeerId())); - } - } - } - - if (clientMap.size() > 0) - { - connectServiceAddClients(activity, clientMap); - } - } - - private void connectServiceAddClients(Activity activity, List clientMap) - { - ServiceConnection connection = new ServiceConnection() - { - @Override - public void onServiceConnected(ComponentName name, IBinder service) - { - WalletConnectService walletConnectService = ((WalletConnectService.LocalBinder) service).getService(); - walletConnectService.addClients(clientMap); - } - - @Override - public void onServiceDisconnected(ComponentName name) - { - Timber.d("Service disconnected"); - } - }; - - WCUtils.startServiceLocal(activity, connection, WalletConnectActions.CONNECT); - } - public boolean checkNewWallet(String address) { return preferenceRepository.isNewWallet(address); diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java index ea9450a730..79335b1d90 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/WalletConnectViewModel.java @@ -40,7 +40,6 @@ import com.alphawallet.app.service.RealmManager; import com.alphawallet.app.service.TokensService; import com.alphawallet.app.service.TransactionSendHandlerInterface; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.walletconnect.AWWalletConnectClient; import com.alphawallet.app.walletconnect.WCClient; import com.alphawallet.app.walletconnect.WCSession; diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/WCClient.kt b/app/src/main/java/com/alphawallet/app/walletconnect/WCClient.kt index 791f38ca34..a4f688c30d 100644 --- a/app/src/main/java/com/alphawallet/app/walletconnect/WCClient.kt +++ b/app/src/main/java/com/alphawallet/app/walletconnect/WCClient.kt @@ -1,22 +1,37 @@ package com.alphawallet.app.walletconnect import com.alphawallet.app.C -import com.alphawallet.app.walletconnect.entity.* +import com.alphawallet.app.walletconnect.entity.InvalidJsonRpcParamsException +import com.alphawallet.app.walletconnect.entity.JsonRpcError +import com.alphawallet.app.walletconnect.entity.JsonRpcErrorResponse +import com.alphawallet.app.walletconnect.entity.JsonRpcRequest +import com.alphawallet.app.walletconnect.entity.MessageType +import com.alphawallet.app.walletconnect.entity.WCEncryptionPayload +import com.alphawallet.app.walletconnect.entity.WCEthereumSignMessage +import com.alphawallet.app.walletconnect.entity.WCEthereumTransaction +import com.alphawallet.app.walletconnect.entity.WCMethod +import com.alphawallet.app.walletconnect.entity.WCPeerMeta +import com.alphawallet.app.walletconnect.entity.WCSessionUpdate +import com.alphawallet.app.walletconnect.entity.WCSocketMessage +import com.alphawallet.app.walletconnect.entity.ethTransactionSerializer import com.alphawallet.app.walletconnect.util.WCCipher import com.alphawallet.app.walletconnect.util.toByteArray import com.alphawallet.app.web3.entity.WalletAddEthereumChainObject -import com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.registerTypeAdapter import com.github.salomonbrys.kotson.typeToken import com.google.gson.GsonBuilder import com.google.gson.JsonArray import com.google.gson.JsonSyntaxException -import okhttp3.* +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener import okio.ByteString -import org.web3j.utils.Numeric import timber.log.Timber -import java.util.* +import java.util.Date +import java.util.UUID import java.util.concurrent.TimeUnit open class WCClient : WebSocketListener() { @@ -52,8 +67,6 @@ open class WCClient : WebSocketListener() { else return null } - private var handshakeId: Long = -1 - var accounts: List? = null private set @@ -67,10 +80,6 @@ open class WCClient : WebSocketListener() { .retryOnConnectionFailure(true) .build(); - fun chainIdVal(): Long { - return chainId?.toLong() ?: 0 - } - var onFailure: (Throwable) -> Unit = { _ -> Unit } var onDisconnect: (code: Int, reason: String) -> Unit = { _, _ -> Unit } var onSessionRequest: (id: Long, peer: WCPeerMeta) -> Unit = { _, _ -> Unit } @@ -94,9 +103,9 @@ open class WCClient : WebSocketListener() { listeners.forEach { it.onOpen(webSocket, response) } val session = - this.session ?: throw IllegalStateException("session can't be null on connection open") + this.session!! val peerId = - this.peerId ?: throw IllegalStateException("peerId can't be null on connection open") + this.peerId!! // The Session.topic channel is used to listen session request messages only. subscribe(session.topic) // The peerId channel is used to listen to all messages sent to this httpClient. @@ -172,39 +181,6 @@ open class WCClient : WebSocketListener() { socket = httpClient.newWebSocket(request, this) } - fun setupSession(accounts: List, _chainId: Long) { - this.chainId = _chainId.toString(); - this.accounts = accounts; - } - - fun approveSession(accounts: List, _chainId: Long): Boolean { - if (handshakeId <= 0) { - onFailure(Throwable("handshakeId must be greater than 0 on session approve")) - } - var useChainId: Long = _chainId - if (this.chainId?.toIntOrNull() != 1) useChainId = _chainId - chainId = useChainId.toString() - this.accounts = accounts; - - val result = WCApproveSessionResponse( - chainId = useChainId, - accounts = accounts, - peerId = peerId, - peerMeta = peerMeta - ) - val response = JsonRpcResponse( - id = handshakeId, - result = result - ) - - return encryptAndSend(gson.toJson(response)) - } - - fun sendPing(): Boolean { - Timber.d("==> ping") - return socket?.send("ping") ?: false - } - fun updateSession( accounts: List? = null, chainId: Long? = null, @@ -224,114 +200,15 @@ open class WCClient : WebSocketListener() { return encryptAndSend(gson.toJson(request)) } - fun rejectSession(message: String = "Session rejected"): Boolean { - check(handshakeId > 0) { "handshakeId must be greater than 0" } - val response = JsonRpcErrorResponse( - id = handshakeId, - error = JsonRpcError.serverError( - message = message - ) - ) - return encryptAndSend(gson.toJson(response)) - } - fun killSession(): Boolean { updateSession(approved = false) return disconnect() } - fun approveRequest(id: Long, result: T): Boolean { - val response = JsonRpcResponse( - id = id, - result = result - ) - return encryptAndSend(gson.toJson(response)) - } - - fun rejectRequest(id: Long, message: String = "Rejected by the user"): Boolean { - val response = JsonRpcErrorResponse( - id = id, - error = JsonRpcError.serverError( - message = message - ) - ) - return encryptAndSend(gson.toJson(response)) - } - - fun switchChain( - requestId: Long, - chainId: Long, - success: Boolean, - chainAvailable: Boolean = true - ): Boolean { - Timber.tag(TAG).d( - "switchChain: id: %s, chainId: %s, success: %s, chainAvailable: %s", - requestId, - chainId, - success, - chainAvailable - ); - var response:String; - if (!chainAvailable) { - val errorResponse = JsonRpcErrorResponse( - id = requestId, - error = JsonRpcError.unrecognisedChain( - message = "Unrecognized chain ID" - ) - ) - return encryptAndSend(gson.toJson(errorResponse)) - } - if (success) { - this.chainId = chainId.toString(); - updateSession() // will use updated chainId - response = gson.toJson(JsonRpcResponse( - id = requestId, // json rpc request id - result = null - )) - } else { - response = gson.toJson(JsonRpcErrorResponse( - id = requestId, - error = JsonRpcError.serverError( - message = "Rejected by user" - )) - ) - } - return encryptAndSend(response) - } - - fun addChain( - requestId: Long, - chainObj: WalletAddEthereumChainObject, - success: Boolean - ): Boolean { - Timber.tag(TAG).d( - "addChain: id: %s, chainId: %s, success: %s", - requestId, - chainObj.getChainId(), - success - ); - return if (success) { - this.chainId = chainObj.getChainId().toString() - updateSession() // updated session with new chain Id - encryptAndSend( - gson.toJson( - JsonRpcResponse(id = requestId, result = null) - ) - ) - } else { - val errorResponse = JsonRpcErrorResponse( - id = requestId, - error = JsonRpcError.serverError("Rejected by user") - ) - encryptAndSend(gson.toJson(errorResponse)) - } - } - private fun decryptMessage(text: String): String { val message = gson.fromJson(text) val encrypted = gson.fromJson(message.payload) - val session = this.session - ?: throw IllegalStateException("Session is null") + val session = this.session!! return String(WCCipher.decrypt(encrypted, session.key.toByteArray()), Charsets.UTF_8) } @@ -365,91 +242,6 @@ open class WCClient : WebSocketListener() { private fun handleRequest(request: JsonRpcRequest) { Timber.tag(TAG).d("handleRequest: %s", request.toString()) - when (request.method) { - WCMethod.SESSION_REQUEST -> { - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - handshakeId = request.id - remotePeerId = param.peerId - chainId = when (param.chainId.isNullOrEmpty()) { - true -> MAINNET_ID.toString() - false -> param.chainId - } - onSessionRequest(request.id, param.peerMeta) - } - WCMethod.SESSION_UPDATE -> { - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - if (!param.approved) { - killSession() - } - } - WCMethod.ETH_SIGN -> { - signRequest(request, WCEthereumSignMessage.WCSignType.MESSAGE) - } - WCMethod.ETH_PERSONAL_SIGN -> { - signRequest(request, WCEthereumSignMessage.WCSignType.PERSONAL_MESSAGE) - } - WCMethod.ETH_SIGN_TYPE_DATA, - WCMethod.ETH_SIGN_TYPE_DATA_V3, - WCMethod.ETH_SIGN_TYPE_DATA_V4 -> { - signRequest(request, WCEthereumSignMessage.WCSignType.TYPED_MESSAGE) - } - WCMethod.ETH_SIGN_TRANSACTION -> { - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - onEthSignTransaction(request.id, param) - } - WCMethod.ETH_SEND_TRANSACTION -> { - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - onEthSendTransaction(request.id, param) - } - WCMethod.GET_ACCOUNTS -> { - onGetAccounts(request.id) - } - WCMethod.SWITCH_ETHEREUM_CHAIN -> { - handleSwitchChain(request) - } - WCMethod.ADD_ETHEREUM_CHAIN -> { - handleAddChain(request) - } - else -> {} - } - } - - private fun handleAddChain(request: JsonRpcRequest) - { - Timber.d("WCMethod: addEthereumChain") - val param: WCAddEthChain = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - Timber.tag(TAG).d("addChainRequest: $param") - onAddEthereumChain(request.id, param.toWalletAddEthereumObject()) - } - - private fun handleSwitchChain(request: JsonRpcRequest) - { - Timber.d("WCMethod: switchEthereumChain") - val param = gson.fromJson>(request.params) - .firstOrNull() ?: throw InvalidJsonRpcParamsException(request.id) - val newChainId: Long = Numeric.toBigInt(param.chainId).toLong(); - if (newChainId != chainIdVal()) { - onSwitchEthereumChain(request.id, newChainId) - } else { - // auto accept if same chain - switchChain(request.id, chainIdVal(), true); - } - } - - private fun signRequest(request: JsonRpcRequest, signType: WCEthereumSignMessage.WCSignType) - { - val params = gson.fromJson>(request.params) - if (params.size < 2) - throw InvalidJsonRpcParamsException(request.id) - onEthSign( - request.id, - WCEthereumSignMessage(params, signType) - ) } private fun subscribe(topic: String): Boolean { @@ -466,8 +258,7 @@ open class WCClient : WebSocketListener() { private fun encryptAndSend(result: String): Boolean { Timber.d("==> message $result") - val session = this.session - ?: throw IllegalStateException("Session is null") + val session = this.session!! val payload = gson.toJson( WCCipher.encrypt( result.toByteArray(Charsets.UTF_8), @@ -491,21 +282,4 @@ open class WCClient : WebSocketListener() { fun disconnect(): Boolean { return socket?.close(1000, null) ?: false } - - fun addSocketListener(listener: WebSocketListener) { - listeners.add(listener) - } - - fun removeSocketListener(listener: WebSocketListener) { - listeners.remove(listener) - } - - fun resetState() { - handshakeId = -1 - isConnected = false - session = null - peerId = null - remotePeerId = null - peerMeta = null - } } diff --git a/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java b/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java index 72f8b0ea36..761003fc5e 100644 --- a/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java +++ b/app/src/main/java/com/alphawallet/app/walletconnect/entity/WCUtils.java @@ -10,8 +10,6 @@ import com.alphawallet.app.R; import com.alphawallet.app.entity.Wallet; import com.alphawallet.app.entity.WalletConnectActions; -import com.alphawallet.app.repository.entity.RealmWCSession; -import com.alphawallet.app.service.WalletConnectService; import com.alphawallet.app.walletconnect.WCClient; import com.alphawallet.app.walletconnect.WCSession; diff --git a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java index 68f971897b..8a9e546fa6 100644 --- a/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java +++ b/app/src/main/java/com/alphawallet/app/widget/ActionSheetDialog.java @@ -37,7 +37,6 @@ import com.alphawallet.app.service.TokensService; import com.alphawallet.app.ui.HomeActivity; import com.alphawallet.app.ui.TransactionSuccessActivity; -import com.alphawallet.app.ui.WalletConnectActivity; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; import com.alphawallet.app.ui.widget.entity.GasWidgetInterface; import com.alphawallet.app.util.Utils; @@ -128,10 +127,6 @@ public ActionSheetDialog(@NonNull Activity activity, Web3Transaction tx, Token t { mode = ActionSheetMode.SEND_TRANSACTION_DAPP; } - else if (activity instanceof WalletConnectActivity) - { - mode = ActionSheetMode.SEND_TRANSACTION_WC; - } else { mode = ActionSheetMode.SEND_TRANSACTION; @@ -845,13 +840,6 @@ public void gotSignature(SignatureFromKey signature) actionSheetCallback.getAuthorisation(signCallback); } - //Takes gas estimate from calling activity (eg WalletConnectActivity) and updates dialog -// public void setGasEstimate(BigInteger estimate) -// { -// gasWidgetInterface.setGasEstimate(estimate); -// functionBar.setPrimaryButtonEnabled(true); -// } - @Override public void setGasEstimate(GasEstimate estimate) {