diff --git a/metamask-android-sdk/build.gradle b/metamask-android-sdk/build.gradle index a4eee650..44e5dddc 100644 --- a/metamask-android-sdk/build.gradle +++ b/metamask-android-sdk/build.gradle @@ -15,7 +15,7 @@ android { targetSdk 33 ext.versionCode = 1 - ext.versionName = "0.6.4" + ext.versionName = "0.6.5" testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' consumerProguardFiles 'consumer-rules.pro' @@ -53,7 +53,6 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.test.ext:junit-ktx:1.1.5' implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.6.1" testImplementation 'junit:junit:4.13.2' @@ -68,7 +67,7 @@ dependencies { ext { PUBLISH_GROUP_ID = 'io.metamask.androidsdk' - PUBLISH_VERSION = '0.6.4' + PUBLISH_VERSION = '0.6.5' PUBLISH_ARTIFACT_ID = 'metamask-android-sdk' } diff --git a/metamask-android-sdk/src/main/java/io/metamask/androidsdk/ReadOnlyRPCProvider.kt b/metamask-android-sdk/src/main/java/io/metamask/androidsdk/ReadOnlyRPCProvider.kt index c041b9d9..76ba7c94 100644 --- a/metamask-android-sdk/src/main/java/io/metamask/androidsdk/ReadOnlyRPCProvider.kt +++ b/metamask-android-sdk/src/main/java/io/metamask/androidsdk/ReadOnlyRPCProvider.kt @@ -2,7 +2,7 @@ package io.metamask.androidsdk import org.json.JSONObject -open class ReadOnlyRPCProvider(private val infuraAPIKey: String?, readonlyRPCMap: Map?, private val logger: Logger = DefaultLogger) { +open class ReadOnlyRPCProvider(private val infuraAPIKey: String?, private val readonlyRPCMap: Map?, private val logger: Logger = DefaultLogger) { val rpcUrls: Map = when { readonlyRPCMap != null && infuraAPIKey != null -> { // Merge infuraReadonlyRPCMap with readonlyRPCMap, overriding infura's keys if they are present in readonlyRPCMap @@ -16,14 +16,16 @@ open class ReadOnlyRPCProvider(private val infuraAPIKey: String?, readonlyRPCMap } fun supportsChain(chainId: String): Boolean { - return !rpcUrls[chainId].isNullOrEmpty() + val apiKey = infuraAPIKey ?: "" + val readonlyMap = readonlyRPCMap ?: mapOf() + return !rpcUrls[chainId].isNullOrEmpty() && (readonlyMap.containsKey(chainId) || apiKey.isNotEmpty()) } fun infuraReadonlyRPCMap(infuraAPIKey: String) : Map { return mapOf( // ###### Ethereum ###### // Mainnet - //"0x1" to "https://mainnet.infura.io/v3/${infuraAPIKey}", + "0x1" to "https://mainnet.infura.io/v3/${infuraAPIKey}", // Sepolia 11155111 "0x2a" to "https://sepolia.infura.io/v3/${infuraAPIKey}", diff --git a/metamask-android-sdk/src/main/java/io/metamask/androidsdk/SDKInfo.kt b/metamask-android-sdk/src/main/java/io/metamask/androidsdk/SDKInfo.kt index 947ab66b..61072275 100644 --- a/metamask-android-sdk/src/main/java/io/metamask/androidsdk/SDKInfo.kt +++ b/metamask-android-sdk/src/main/java/io/metamask/androidsdk/SDKInfo.kt @@ -1,6 +1,6 @@ package io.metamask.androidsdk object SDKInfo { - const val VERSION = "0.6.4" + const val VERSION = "0.6.5" const val PLATFORM = "android" } \ No newline at end of file diff --git a/metamask-android-sdk/src/test/java/io/metamask/androidsdk/EthereumTests.kt b/metamask-android-sdk/src/test/java/io/metamask/androidsdk/EthereumTests.kt index 384470b5..544b8068 100644 --- a/metamask-android-sdk/src/test/java/io/metamask/androidsdk/EthereumTests.kt +++ b/metamask-android-sdk/src/test/java/io/metamask/androidsdk/EthereumTests.kt @@ -264,7 +264,7 @@ class EthereumTests { private fun findRequestIdForAccountRequest(method: EthereumMethod): String { return communicationClient.submittedRequests.entries.find { it.value.request.method == method.value - }?.key ?: throw IllegalStateException("No account request found") + }?.key ?: "" } private fun prepareCommunicationClient() { diff --git a/nativesdk/README.md b/nativesdk/README.md index 2698b653..9c700f7f 100644 --- a/nativesdk/README.md +++ b/nativesdk/README.md @@ -5,6 +5,19 @@ The MetaMask Android SDK Native Module is a Kotlin [Android Native Module](https This module handles encrypted communication between the dapp and MetaMask and then relays the messages over to the Android SDK communication layer implemented in React Native in the wallet. The wallet calls the Native Module via NativeModules - an API that enables react-native code to call native Kotlin primitives. +## Compiling +To compile the nativesdk as a `.aar` file, you need to modify the project's `settings.gradle` file to have the nativesdk as a standalone module to include to the project: +``` +include ':app' +include ':nativesdk' +include ':metamask-android-sdk' +``` +Then run +``` +./gradlew assembleDebug +``` +The output file will be located in `nativesdk/build/outputs/aar`. This nativesdk library is then embedded in the MetaMask mobile in the path `android/libs/nativesdk.aar`. It becomes the server-side component of the AIDL communication mechanism described below in the Architecture section. + ## Architecture The client SDK communicates with the server SDK (Android Native Module) via IPC implemented using AIDL. diff --git a/nativesdk/build.gradle.kts b/nativesdk/build.gradle.kts index e4e6ca6a..63153fb8 100644 --- a/nativesdk/build.gradle.kts +++ b/nativesdk/build.gradle.kts @@ -36,13 +36,12 @@ android { dependencies { compileOnly(files("libs/ecies.aar")) - implementation("com.facebook.react:react-native:+") + implementation("com.facebook.react:react-android:0.73.3") implementation("androidx.core:core-ktx:1.7.0") implementation ("com.squareup.okhttp3:okhttp:4.9.2") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.9.0") testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") } \ No newline at end of file diff --git a/nativesdk/src/main/java/io/metamask/nativesdk/CommunicationClient.kt b/nativesdk/src/main/java/io/metamask/nativesdk/CommunicationClient.kt index d913bb9d..00560cf4 100644 --- a/nativesdk/src/main/java/io/metamask/nativesdk/CommunicationClient.kt +++ b/nativesdk/src/main/java/io/metamask/nativesdk/CommunicationClient.kt @@ -97,15 +97,6 @@ class CommunicationClient(reactContext: ReactApplicationContext) : ReactContextB connectionStatusManager.onMetaMaskBroadcastUnregistered() } - override fun invalidate() { - super.invalidate() - Logger.log("CommunicationClient:: invalidate - app terminated") - - if (!didPerformTearDown) { - destroyActiveConnections() - } - } - override fun onCatalystInstanceDestroy() { super.onCatalystInstanceDestroy() Logger.log("CommunicationClient:: onCatalystInstanceDestroy - app terminating") @@ -142,13 +133,14 @@ class CommunicationClient(reactContext: ReactApplicationContext) : ReactContextB Logger.log("CommunicationClient:: Binding native module") val intent = Intent(reactAppContext, MessageService::class.java) - return reactAppContext.bindService( + val success = reactAppContext.bindService( intent, serviceConnection, Context.BIND_IMPORTANT) // Metamask ready, start sending messages connectionStatusManager.onMetaMaskReady() + return success } @ReactMethod @@ -184,7 +176,7 @@ class CommunicationClient(reactContext: ReactApplicationContext) : ReactContextB if (type == "ready") { val dataJson = json.optJSONObject("data") - val id = dataJson.optString("id") + val id = dataJson?.optString("id") val sessionId = SessionManager.getInstance().sessionId if (id != sessionId) { diff --git a/nativesdk/src/main/java/io/metamask/nativesdk/KeyExchange.kt b/nativesdk/src/main/java/io/metamask/nativesdk/KeyExchange.kt index 2b860660..cc0cd2e0 100644 --- a/nativesdk/src/main/java/io/metamask/nativesdk/KeyExchange.kt +++ b/nativesdk/src/main/java/io/metamask/nativesdk/KeyExchange.kt @@ -48,9 +48,7 @@ class KeyExchange { fun resetKeys() { privateKey = crypto.generatePrivateKey() - privateKey?.let { - publicKey = crypto.publicKey(it) - } + publicKey = crypto.publicKey(privateKey) setIsKeysExchanged(false) theirPublicKey = null } diff --git a/nativesdk/src/main/java/io/metamask/nativesdk/MessageService.kt b/nativesdk/src/main/java/io/metamask/nativesdk/MessageService.kt index d0b5c125..f4a36f57 100644 --- a/nativesdk/src/main/java/io/metamask/nativesdk/MessageService.kt +++ b/nativesdk/src/main/java/io/metamask/nativesdk/MessageService.kt @@ -162,11 +162,17 @@ class MessageService : Service(), MetaMaskConnectionStatusCallback { } } - private fun sendMessage(message: String) { + private fun sendMessage(key: String = MESSAGE, message: String) { val bundle = Bundle().apply { - putString(MESSAGE, message) + putString(key, message) + } + try { + synchronized(this@MessageService) { + dappMessageServiceCallback?.onMessageReceived(bundle) + } + } catch (e: Exception) { + Logger.log("MessageService:: Error sending message to dapp: ${e.message}") } - dappMessageServiceCallback?.onMessageReceived(bundle) } private fun handleKeyExchange(message: String) { @@ -199,7 +205,7 @@ class MessageService : Service(), MetaMaskConnectionStatusCallback { Logger.log("MessageService:: $keysExchangedMessage") val payload = keyExchange.encrypt(keysExchangedMessage) - sendMessage(payload) + sendMessage(message=payload) } } @@ -248,18 +254,17 @@ class MessageService : Service(), MetaMaskConnectionStatusCallback { private fun resumeQueuedJobs() { Logger.log("MessageService:: Resuming jobs") - while (jobQueue.isNotEmpty()) { - val job = jobQueue.removeFirstOrNull() - job?.invoke() + synchronized(this) { + while (jobQueue.isNotEmpty()) { + val job = jobQueue.removeFirstOrNull() + job?.invoke() + } } } private fun sendKeyExchangeMessage(message: String) { Logger.log("MessageService:: Sending key exchange: $message") - val bundle = Bundle().apply { - putString(KEY_EXCHANGE, message) - } - dappMessageServiceCallback?.onMessageReceived(bundle) + sendMessage(key=KEY_EXCHANGE, message=message) } private fun handleMessage(message: String, id: String) { @@ -329,12 +334,7 @@ class MessageService : Service(), MetaMaskConnectionStatusCallback { override fun onReceive(context: Context, intent: Intent) { if (intent.action == "local.service") { val message = intent.getStringExtra(EventType.MESSAGE.value) as String - - val bundle = Bundle().apply { - putString(EventType.MESSAGE.value, message) - } - - dappMessageServiceCallback?.onMessageReceived(bundle) + sendMessage(message=message) } } }