diff --git a/README.md b/README.md index 66c5203ce6..57dc5a363a 100644 --- a/README.md +++ b/README.md @@ -9,69 +9,241 @@ Kotlin implementation of WalletConnect v2 protocol for Android applications. * Java 11 ## Installation -root/build.gradle: +root/build.gradle.kts: ```gradle allprojects { repositories { - maven { url "https://jitpack.io" } + maven(url = "https://jitpack.io") } } ```
-app/build.gradle(.kts) +app/build.gradle ```gradle -groovy - implementation 'com.walletconnect:walletconnectv2:1.0.0-alpha01' - -kotlin - implementation("com.walletconnect:walletconnectv2:1.0.0-alpha01") - +implementation("com.walletconnect:walletconnectv2:1.0.0-beta01") ``` ## **Usage** ### **Initialize WalletConnect Client** ```kotlin -val initializeParams = ClientTypes.InitialParams(useTls = true, hostName = "relay.walletconnect.org", apiKey = "sample key", isController = true) +val appMetaData = AppMetaData(name = "Wallet Name", description = "Wallet Description", url = "Wallet Url", icons = listOfIconUrlStrings) +val initializeParams = ClientTypes.InitialParams(application = application, projectId = "project id", appMetaData = appMetaData) WalletConnectClient.initalize(initalizeParams) ``` -The controller client will always be the "wallet" which is exposing blockchain accounts to a "Dapp" and therefore is also in charge of signing. -To initialize the WalletConnect client, create a ClientTypes.InitialParams object in the Android Application class. The InitialParams object will need at least the API key and the Application. The InitialParams object will then be passed to the WalletConnect.initialize function. + +The controller client will always be the wallet which is exposing blockchain accounts to a Dapp and therefore is also in charge of signing. +To initialize the WalletConnect client, create a `ClientTypes.InitialParams` object in the Android Application class. The InitialParams object will need at least the application class, the ProjectID and the wallet's AppMetaData. The InitialParams object will then be passed to the `WalletConnectClient` initialize function. IntitalParams also allows for custom URLs by passing URL string into the `hostName` property. + +### **WalletConnectClientListeners.Session Listeners** +```kotlin +val listener = object: WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + // Session Proposal object sent by Dapp after pairing was successful + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) { + // JSON-RPC methods wrapped by SessionRequest object sent by Dapp + } + + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) { + // Triggered when the session is deleted by the peer + } + + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) { + // Triggered when the peer emits events as notifications that match the list of types agreed upon session settlement + } +} +WalletConnectClient.setWalletConnectListener(listener) +``` + +The WalletConnectClient needs a `WalletConnectClientListener` passed to it for it to be able to expose asynchronously updates sent from the Dapp. ### **Pair Clients** ```kotlin val pairParams = ClientTypes.PairParams("wc:...") -val pairListener = WalletConnectClientListeners.Pairing { sessionProposal -> /* handle session proposal */ } +val pairListener = object: WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + // Settled pairing + } + + override fun onError(error: Throwable) { + // Pairing approval error + } +} WalletConnectClient.pair(pairParams, pairListener) ``` -To pair the wallet with the Dapp, call the WalletConnectClient.pair function which needs a ClientTypes.PairParams and WalletConnectClientListeners.Pairing. +To pair the wallet with the Dapp, call the WalletConnectClient.pair function which needs a `ClientTypes.PairParams` and `WalletConnectClientListeners.Pairing`. ClientTypes.Params is where the Dapp Uri will be passed. -WalletConnectClientListeners.Pairing is the callback that will be asynchronously called once there a pairing has been made with the Dapp. A SessionProposal object is returned once a pairing is made. +WalletConnectClientListeners.Pairing is the callback that will be asynchronously called once there a pairing has been made with the Dapp. ### **Session Approval** +NOTE: addresses provided in `accounts` array should follow [CAPI10](https://github.com/ChainAgnostic/CAIPs/blob/master/CAIPs/caip-10.md) semantics. ```kotlin val accounts: List = /*list of accounts on chains*/ -val proposerPublicKey: String = /*proposerPublicKey from the Session Proposal*/ -val proposalTtl: Long = /*Ttl from the Session Proposal*/ -val proposalTopic: String = /*Topic from the Session Proposal*/ -val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(accounts, proposerPublicKey, proposalTtl, proposalTopic) - -WalletConnectClient.approve(approveParams) +val sessionProposal: WalletConnectClientData = /*Session Proposal object*/ +val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts) +val listener: WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + // Approve session success + } + + override fun onError(error: Throwable) { + // Approve session error + } +} +WalletConnectClient.approve(approveParams, listener) ``` -To send an approval for the Session Proposal, pass the Session Proposal public key, ttl, and topic along with the list of accounts to the WalletConnectClient.approve function. + +To send an approval, pass a Session Proposal object along with the list of accounts to the `WalletConnectClient.approve` function. Listener will asynchronously expose the settled session if the operation is successful. ### **Session Rejection** ```kotlin val rejectionReason: String = /*The reason for rejecting the Session Proposal*/ val proposalTopic: String = /*Topic from the Session Proposal*/ val rejectParams: ClientTypes.RejectParams = ClientTypes.RejectParams(rejectionReason, proposalTopic) +val listener: WalletConnectClientListneners.SessionReject { + override fun onSuccess(rejectedSession: WalletConnectClientData.RejectedSession) { + // Rejection proposal + } + + override fun onError(error: Throwable) { + //Rejected proposal error + } +} +WalletConnectClient.reject(rejectParams, listener) +``` +To send a rejection for the Session Proposal, pass a rejection reason and the Session Proposal topic to the `WalletConnectClient.reject` function. Listener will asynchronously expose a `RejectedSession` object that will mirror the data sent for rejection. + +### **Session Disconnect** +```kotlin +val disconnectionReason: String = /*The reason for disconnecting the Settled Session*/ +val sessionTopic: String = /*Topic from the Settled Session*/ +val disconnectParams = ClientTypes.DisconnectParams(sessionTopic, disconnectionReason) +val listener = object : WalletConnectClientListeners.SessionDelete { + override fun onSuccess(deletedSession: WalletConnectClientData.DeletedSession) { + // DeleteSession object with topic and reason + } + + override fun onError(error: Throwable) { + // Session disconnect error + } +} + +WalletConnectClient.disconnect(disconnectParams, listener) +``` +To disconnect from a settle session, pass a disconnection reason and the Settled Session topic to the `WalletConnectClient.disconnect` function. Listener will asynchronously expose a DeleteSession object that will mirror the data sent for rejection. + +### **Respond Request** +```kotlin +val sessionRequestTopic: String = /*Topic of Settled Session*/ +val jsonRpcResponse: WalletConnectClientData.JsonRpcResponse.JsonRpcResult = /*Settled Session Request ID along with request data*/ +val result = ClientTypes.ResponseParams(sessionTopic = sessionRequestTopic, jsonRpcResponse = jsonRpcResponse) +val listener = object : WalletConnectClientListeners.SessionPayload { + override fun onError(error: Throwable) { + // Error + } +} + +WalletConnectClient.respond(result, listener) +``` +To respond to JSON-RPC methods that were sent from Dapps for a settle session, submit a `ClientTypes.ResponseParams` with the settled session's topic and request ID along with the respond data to the `WalletConnectClient.respond` function. Any errors would exposed through the `WalletConnectClientListeners.SessionPayload` listener. + +### **Reject Request** +```kotlin +val sessionRequestTopic: String = /*Topic of Settled Session*/ +val jsonRpcResponseError: WalletConnectClientData.JsonRpcResponse.JsonRpcError = /*Settled Session Request ID along with error code and message*/ +val result = ClientTypes.ResponseParams(sessionTopic = sessionRequestTopic, jsonRpcResponse = jsonRpcResponseError) +val listener = object : WalletConnectClientListeners.SessionPayload { + override fun onError(error: Throwable) { + // Error + } +} + +WalletConnectClient.respond(result, listener) +``` +To reject a JSON-RPC method that was sent from a Dapps for a settle session, submit a `ClientTypes.ResponseParams` with the settled session's topic and request ID along with the rejection data to the `WalletConnectClient.respond` function. Any errors would exposed through the `WalletConnectClientListeners.SessionPayload` listener. + +### **Session Update** +```kotlin +val sessionTopic: String = /*Topic of Settled Session*/ +val sessionState: WalletConnectClientData.SessionState = /*object with list of accounts to update*/ +val updateParams = ClientTypes.UpdateParams(sessionTopic = sessionTopic, sessionState = sessionState) +val listener = object : WalletConnectClientListeners.SessionUpdate { + override fun onSuccess(updatedSession: WalletConnectClientData.UpdatedSession) { + // Callback for when Dapps successfully updates settled session + } + + override fun onError(error: Throwable) { + // Error + } +} + +WalletConnectClient.update(updateParams, listener) +``` +To update a settled session, create a `ClientTypes.UpdateParams` object with the settled session's topic and accounts to update session with to `WalletConnectClient.update`. Listener will echo the accounts updated on the Dapp if action is successful. + +### **Session Upgrade** +```kotlin +val sessionTopic: String = /*Topic of Settled Session*/ +val permissions: WalletConnectClientData.SessionPermissions = /*list of blockchains and JSON-RPC methods to upgrade with*/ +val upgradeParams = ClientTypes.UpgradeParams(sessionTopic = sessionTopic, permissions = permissions) +val listener = object : WalletConnectClientListeners.SessionUpgrade { + override fun onSuccess(upgradedSession: WalletConnectClientData.UpgradedSession) { + // Callback for when Dapps successfully upgrades settled session + } + + override fun onError(error: Throwable) { + // Error + } +} -WalletConnectClient.reject(rejectParams) +WalletConnectClient.upgrade(upgradeParams, listener) ``` -To send a rejection for the Session Proposal, pass a rejection reason and the Session Proposal public key to the WalletConnectClient.approve function. +To upgrade a settled session, create a `ClientTypes.UpgradeParams` object with the settled session's topic and blockchains and JSON-RPC methods to upgrade the session with to `WalletConnectClient.upgrade`. Listener will echo the blockchains and JSON-RPC methods upgraded on the Dapp if action is successful. + +### **Session Ping** +```kotlin +val sessionTopic: String = /*Topic of Settled Session*/ +val pingParams = ClientTypes.PingParams(sessionTopic) +val listener = object : WalletConnectClientListeners.SessionPing { + override fun onSuccess(topic: String) { + // Topic being pinged + } + + override fun onError(error: Throwable) { + // Error + } +} + +WalletConnectClient.ping(pingParams, listener) +``` +To ping a Dapp with a settled session, call `WalletConnectClient.ping` with the `ClientTypes.PingParams` with a settle session's topic. If ping is successful, topic is echo'd in listener. + +### **Get List of Settled Sessions** +```kotlin +WalletConnectClient.getListOfSettledSessions() +``` +To get a list of the most current setteld sessions, call `WalletConnectClient.getListOfSettledSessions()` which will return a list of type `WalletConnectClientData.SettledSession`. + +### **Get List of Pending Sessions** +```kotlin +WalletConnectClient.getListOfPendingSession() +``` +To get a list of the most current pending sessions, call `WalletConnectClient.getListOfPendingSession()` which will return a list of type `WalletConnectClientData.SessionProposal`. + +### **Shutdown SDK** +```kotlin +WalletConnectClient.shutdown() +``` +To make sure that the internal coroutines are handled correctly when leaving the application, call `WalletConnectClient.shutdown()` before exiting from the application. +
+ +## API Keys -### **Contributing** -Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. +For api keys look at [API Keys](../../api/api-keys.md) \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 284437117d..be472d3b63 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,14 +5,16 @@ buildscript { mavenCentral() } dependencies { - classpath ("com.android.tools.build:gradle:7.0.3") + classpath ("com.android.tools.build:gradle:7.0.4") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + classpath("com.squareup.sqldelight:gradle-plugin:$sqlDelightVersion") } } allprojects { repositories { google() + maven(url = "https://jitpack.io") mavenLocal() mavenCentral() jcenter() // Warning: this repository is going to shut down soon diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 831e7f25b7..4582db5720 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -3,6 +3,7 @@ import org.gradle.kotlin.dsl.DependencyHandlerScope const val kotlinVersion = "1.5.31" val jvmVersion = JavaVersion.VERSION_11 +const val sqlDelightVersion = "1.5.2" fun DependencyHandlerScope.scanner() { val mlKitBarcode = "16.0.1" @@ -21,6 +22,7 @@ fun DependencyHandlerScope.lifecycle() { val lifecycleVersion = "2.3.1" "implementation"("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion") "implementation"("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion") + "implementation"("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01") } fun DependencyHandlerScope.navigationComponent() { @@ -54,11 +56,6 @@ fun DependencyHandlerScope.moshi() { "kapt"("com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion") } -fun DependencyHandlerScope.json() { - val jsonVersion = "20210307" - "implementation"("org.json:json:$jsonVersion") -} - fun DependencyHandlerScope.okhttp() { val okhttpVersion = "4.9.0" @@ -67,23 +64,45 @@ fun DependencyHandlerScope.okhttp() { "implementation"("com.squareup.okhttp3:logging-interceptor") } -fun DependencyHandlerScope.lazySodium() { - val lazySodiumVersion = "5.0.2@aar" - val jnaVersion = "5.8.0@aar" - val slf4jVersion = "1.7.32" +fun DependencyHandlerScope.bouncyCastle() { + val bouncyCastleVersion = "1.70" + "implementation"("org.bouncycastle:bcprov-jdk15on:$bouncyCastleVersion") +} + +fun DependencyHandlerScope.sqlDelight() { + "implementation"("com.squareup.sqldelight:android-driver:$sqlDelightVersion") + "implementation"("com.squareup.sqldelight:coroutines-extensions-jvm:$sqlDelightVersion") - "implementation"("com.goterl:lazysodium-android:$lazySodiumVersion") - "implementation"("net.java.dev.jna:jna:$jnaVersion") - "testImplementation"("org.slf4j:slf4j-nop:$slf4jVersion") + "testImplementation"("com.squareup.sqldelight:sqlite-driver:$sqlDelightVersion") + "testImplementation"("org.xerial:sqlite-jdbc:3.8.10.2") { + // Override the version of sqlite used by sqlite-driver to match Android API 23 + isForce = true + } } fun DependencyHandlerScope.jUnit5() { val jUnit5Version = "5.7.2" + "testImplementation"("androidx.test.ext:junit-ktx:1.1.3") + "testImplementation"("androidx.test:core-ktx:1.4.0") "testImplementation"(platform("org.junit:junit-bom:$jUnit5Version")) "testImplementation"("org.junit.jupiter:junit-jupiter-api:$jUnit5Version") "testRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:$jUnit5Version") "testImplementation"("org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion") + + "androidTestImplementation"("androidx.test:core-ktx:1.4.0") + "androidTestImplementation"("androidx.test:runner:1.4.0") + "androidTestImplementation"("androidx.test:rules:1.4.0") + + "androidTestImplementation"("org.junit.jupiter:junit-jupiter-api:$jUnit5Version") + "androidTestImplementation"("de.mannodermaus.junit5:android-test-core:1.3.0") + "androidTestRuntimeOnly"("de.mannodermaus.junit5:android-test-runner:1.3.0") +} + +fun DependencyHandlerScope.robolectric() { + val robolectricVersion = "4.6" + + "testImplementation"("org.robolectric:robolectric:$robolectricVersion") } fun DependencyHandlerScope.mockk() { @@ -96,4 +115,9 @@ fun DependencyHandlerScope.timber() { val timberVersion = "5.0.1" "implementation"("com.jakewharton.timber:timber:$timberVersion") +} + +fun DependencyHandlerScope.security() { + val androidSecurityVersion = "1.0.0" + "implementation"("androidx.security:security-crypto:$androidSecurityVersion") } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 252175276f..6359048dd2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,10 +10,11 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true + org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app"s APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true +android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official \ No newline at end of file diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000000..46c8529199 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,2 @@ +jdk: + - openjdk11 \ No newline at end of file diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index 68ae9241c8..914b46234e 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -1,5 +1,3 @@ -import org.jetbrains.kotlin.kapt3.base.Kapt.kapt - plugins { id("com.android.application") kotlin("android") @@ -10,8 +8,8 @@ android { compileSdk = 30 defaultConfig { - applicationId = "org.walletconnect.sdk" - minSdk = 21 + applicationId = "com.walletconnect.sample" + minSdk = 23 targetSdk = 30 versionCode = 1 versionName = "1.0" @@ -26,13 +24,16 @@ android { buildTypes { getByName("release") { isMinifyEnabled = false + signingConfig = signingConfigs.getByName("debug") proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } + compileOptions { sourceCompatibility = jvmVersion targetCompatibility = jvmVersion } + kotlinOptions { jvmTarget = jvmVersion.toString() } @@ -40,7 +41,7 @@ android { dependencies { implementation(project(":walletconnectv2")) -// implementation("com.walletconnect:walletconnectv2:1.0.0-alpha01") +// implementation("com.github.walletconnect-labs.walletconnectkotlinv2:walletconnectkotlinv2:1.0.0-alpha01") coroutines() navigationComponent() @@ -48,7 +49,7 @@ dependencies { lifecycle() scanner() - implementation ("com.github.bumptech.glide:glide:4.12.0") + implementation("com.github.bumptech.glide:glide:4.12.0") kapt("com.github.bumptech.glide:compiler:4.12.0") implementation("androidx.appcompat:appcompat:1.3.1") implementation("com.google.android.material:material:1.4.0") diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index 3950c08a39..40701fb6a8 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -1,12 +1,12 @@ + package="com.walletconnect.sample"> + android:name="com.walletconnect.sample.MainActivity" + android:exported="true" + android:screenOrientation="portrait"> diff --git a/sample/src/main/java/org/walletconnect/example/MainActivity.kt b/sample/src/main/java/com/walletconnect/sample/MainActivity.kt similarity index 79% rename from sample/src/main/java/org/walletconnect/example/MainActivity.kt rename to sample/src/main/java/com/walletconnect/sample/MainActivity.kt index aaf6fa1676..1f7edcc21d 100644 --- a/sample/src/main/java/org/walletconnect/example/MainActivity.kt +++ b/sample/src/main/java/com/walletconnect/sample/MainActivity.kt @@ -1,4 +1,4 @@ -package org.walletconnect.example +package com.walletconnect.sample import android.os.Bundle import androidx.appcompat.app.AppCompatActivity @@ -6,7 +6,8 @@ import androidx.core.view.isGone import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.findNavController import androidx.navigation.ui.NavigationUI -import org.walletconnect.example.databinding.ActivityMainBinding +import com.walletconnect.sample.databinding.ActivityMainBinding +import com.walletconnect.walletconnectv2.WalletConnectClient class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding @@ -18,6 +19,12 @@ class MainActivity : AppCompatActivity() { setBottomNavigation() } + override fun onDestroy() { + super.onDestroy() + + WalletConnectClient.shutdown() + } + private fun setBottomNavigation() { val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_container) as NavHostFragment NavigationUI.setupWithNavController(binding.bottomNav, navHostFragment.findNavController()) diff --git a/sample/src/main/java/com/walletconnect/sample/SampleApplication.kt b/sample/src/main/java/com/walletconnect/sample/SampleApplication.kt new file mode 100644 index 0000000000..3536e21238 --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/SampleApplication.kt @@ -0,0 +1,26 @@ +package com.walletconnect.sample + +import android.app.Application +import com.walletconnect.walletconnectv2.WalletConnectClient +import com.walletconnect.walletconnectv2.client.ClientTypes +import com.walletconnect.walletconnectv2.common.AppMetaData + +class SampleApplication : Application() { + + override fun onCreate() { + super.onCreate() + + val initParams = ClientTypes.InitialParams( + application = this, + hostName = "relay.walletconnect.org", + metadata = AppMetaData( + name = "Kotlin Wallet", + description = "Wallet description", + url = "example.wallet", + icons = listOf("https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media") + ) + ) + + WalletConnectClient.initialize(initParams) + } +} \ No newline at end of file diff --git a/sample/src/main/java/org/walletconnect/example/dapp/DappFragment.kt b/sample/src/main/java/com/walletconnect/sample/dapp/DappFragment.kt similarity index 69% rename from sample/src/main/java/org/walletconnect/example/dapp/DappFragment.kt rename to sample/src/main/java/com/walletconnect/sample/dapp/DappFragment.kt index 80364dddd1..4179409e30 100644 --- a/sample/src/main/java/org/walletconnect/example/dapp/DappFragment.kt +++ b/sample/src/main/java/com/walletconnect/sample/dapp/DappFragment.kt @@ -1,10 +1,10 @@ -package org.walletconnect.example.dapp +package com.walletconnect.sample.dapp import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment -import org.walletconnect.example.R -import org.walletconnect.example.databinding.DappFragmentBinding +import com.walletconnect.sample.R +import com.walletconnect.sample.databinding.DappFragmentBinding class DappFragment : Fragment(R.layout.dapp_fragment) { @@ -18,8 +18,6 @@ class DappFragment : Fragment(R.layout.dapp_fragment) { private fun setupToolbar() { binding.dappToolbar.title = getString(R.string.app_name) - binding.dappToolbar.setOnMenuItemClickListener { item -> - false - } + binding.dappToolbar.setOnMenuItemClickListener { false } } } \ No newline at end of file diff --git a/sample/src/main/java/com/walletconnect/sample/wallet/SessionActionListener.kt b/sample/src/main/java/com/walletconnect/sample/wallet/SessionActionListener.kt new file mode 100644 index 0000000000..ab41b43f22 --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/wallet/SessionActionListener.kt @@ -0,0 +1,11 @@ +package com.walletconnect.sample.wallet + +import com.walletconnect.walletconnectv2.client.WalletConnectClientData + +interface SessionActionListener { + fun onDisconnect(session: WalletConnectClientData.SettledSession) + fun onUpdate(session: WalletConnectClientData.SettledSession) + fun onUpgrade(session: WalletConnectClientData.SettledSession) + fun onPing(session: WalletConnectClientData.SettledSession) + fun onSessionsDetails(session: WalletConnectClientData.SettledSession) +} \ No newline at end of file diff --git a/sample/src/main/java/com/walletconnect/sample/wallet/WalletFragment.kt b/sample/src/main/java/com/walletconnect/sample/wallet/WalletFragment.kt new file mode 100644 index 0000000000..968b0f23ae --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/wallet/WalletFragment.kt @@ -0,0 +1,108 @@ +package com.walletconnect.sample.wallet + +import android.os.Bundle +import android.view.View +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.walletconnect.sample.R +import com.walletconnect.sample.databinding.WalletFragmentBinding +import com.walletconnect.sample.wallet.ui.* +import kotlinx.coroutines.launch +import com.walletconnect.sample.wallet.ui.dialog.SessionDetailsDialog +import com.walletconnect.sample.wallet.ui.dialog.SessionProposalDialog +import com.walletconnect.sample.wallet.ui.dialog.SessionRequestDialog +import com.walletconnect.sample.wallet.ui.dialog.UrlDialog +import com.walletconnect.walletconnectv2.client.WalletConnectClientData + +class WalletFragment : Fragment(R.layout.wallet_fragment), SessionActionListener { + private val viewModel: WalletViewModel by activityViewModels() + private lateinit var binding: WalletFragmentBinding + private val sessionAdapter = SessionsAdapter(this) + + private var proposalDialog: SessionProposalDialog? = null + private var requestDialog: SessionRequestDialog? = null + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding = WalletFragmentBinding.bind(view) + setupToolbar() + binding.sessions.adapter = sessionAdapter + + lifecycleScope.launch { + viewModel.eventFlow.observe(viewLifecycleOwner) { event -> + when (event) { + is InitSessionsList -> sessionAdapter.updateList(event.sessions) + is ShowSessionProposalDialog -> { + proposalDialog = SessionProposalDialog( + requireContext(), + viewModel::approve, + viewModel::reject, + event.proposal + ) + proposalDialog?.show() + } + is ShowSessionRequestDialog -> { + requestDialog = SessionRequestDialog( + requireContext(), + { sessionRequest -> viewModel.respondRequest(sessionRequest) }, + { sessionRequest -> viewModel.rejectRequest(sessionRequest) }, + event.sessionRequest, + event.session + ) + requestDialog?.show() + } + is UpdateActiveSessions -> { + proposalDialog?.dismiss() + sessionAdapter.updateList(event.sessions) + event.message?.let { + Toast.makeText(requireContext(), it, Toast.LENGTH_SHORT).show() + } + } + is RejectSession -> proposalDialog?.dismiss() + is PingSuccess -> Toast.makeText(requireContext(), "Successful session ping", Toast.LENGTH_SHORT).show() + } + } + } + } + + private fun setupToolbar() { + binding.walletToolbar.title = getString(R.string.app_name) + binding.walletToolbar.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.qrCodeScanner -> { + findNavController().navigate(R.id.action_walletFragment_to_scannerFragment) + true + } + R.id.pasteUri -> { + UrlDialog(requireContext(), pair = viewModel::pair).show() + true + } + else -> false + } + } + } + + override fun onDisconnect(session: WalletConnectClientData.SettledSession) { + viewModel.disconnect(session.topic) + } + + override fun onUpdate(session: WalletConnectClientData.SettledSession) { + viewModel.sessionUpdate(session) + } + + override fun onUpgrade(session: WalletConnectClientData.SettledSession) { + viewModel.sessionUpgrade(session) + } + + override fun onPing(session: WalletConnectClientData.SettledSession) { + viewModel.sessionPing(session) + } + + override fun onSessionsDetails(session: WalletConnectClientData.SettledSession) { + + SessionDetailsDialog(requireContext(), session).show() + } +} \ No newline at end of file diff --git a/sample/src/main/java/com/walletconnect/sample/wallet/WalletViewModel.kt b/sample/src/main/java/com/walletconnect/sample/wallet/WalletViewModel.kt new file mode 100644 index 0000000000..9746dbf7f9 --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/wallet/WalletViewModel.kt @@ -0,0 +1,193 @@ +package com.walletconnect.sample.wallet + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import com.walletconnect.sample.wallet.ui.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import com.walletconnect.walletconnectv2.WalletConnectClient +import com.walletconnect.walletconnectv2.client.ClientTypes +import com.walletconnect.walletconnectv2.client.WalletConnectClientData +import com.walletconnect.walletconnectv2.client.WalletConnectClientListener +import com.walletconnect.walletconnectv2.client.WalletConnectClientListeners + +class WalletViewModel : ViewModel(), WalletConnectClientListener { + private var _eventFlow = MutableStateFlow(InitSessionsList(WalletConnectClient.getListOfSettledSessions())) + val eventFlow: LiveData = _eventFlow.asLiveData() + + private lateinit var proposal: WalletConnectClientData.SessionProposal + + init { + WalletConnectClient.setWalletConnectListener(this) + } + + fun pair(uri: String) { + val pairParams = ClientTypes.PairParams(uri.trim()) + + WalletConnectClient.pair(pairParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + //Settled pairing + } + + override fun onError(error: Throwable) { + //Pairing approval error + } + }) + } + + fun approve() { + val accounts = proposal.chains.map { chainId -> "$chainId:0x022c0c42a80bd19EA4cF0F94c4F9F96645759716" } + val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(proposal, accounts) + + WalletConnectClient.approve(approveParams, object : WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + viewModelScope.launch { _eventFlow.emit(UpdateActiveSessions(WalletConnectClient.getListOfSettledSessions())) } + } + + override fun onError(error: Throwable) { + //Approve session error + } + }) + } + + fun reject() { + val rejectionReason = "Reject Session" + val proposalTopic: String = proposal.topic + val rejectParams: ClientTypes.RejectParams = ClientTypes.RejectParams(rejectionReason, proposalTopic) + + WalletConnectClient.reject(rejectParams, object : WalletConnectClientListeners.SessionReject { + override fun onSuccess(rejectedSession: WalletConnectClientData.RejectedSession) { + viewModelScope.launch { _eventFlow.emit(RejectSession) } + } + + override fun onError(error: Throwable) { + //Reject proposal error + } + }) + } + + fun disconnect(topic: String, reason: String = "Reason") { + val disconnectParams = ClientTypes.DisconnectParams(topic, reason) + + WalletConnectClient.disconnect(disconnectParams, object : WalletConnectClientListeners.SessionDelete { + override fun onSuccess(deletedSession: WalletConnectClientData.DeletedSession) { + viewModelScope.launch { _eventFlow.emit(UpdateActiveSessions(WalletConnectClient.getListOfSettledSessions())) } + } + + override fun onError(error: Throwable) { + //Session disconnect error + } + }) + } + + fun respondRequest(sessionRequest: WalletConnectClientData.SessionRequest) { + val result = ClientTypes.ResponseParams( + sessionTopic = sessionRequest.topic, + jsonRpcResponse = WalletConnectClientData.JsonRpcResponse.JsonRpcResult( + sessionRequest.request.id, + "0xa3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a12d887fd3b17bfdce3481f10bea41f45ba9f709d39ce8325427b57afcfc994cee1b" + ) + ) + + WalletConnectClient.respond(result, object : WalletConnectClientListeners.SessionPayload { + override fun onError(error: Throwable) { + //Error + } + }) + } + + fun rejectRequest(sessionRequest: WalletConnectClientData.SessionRequest) { + val result = ClientTypes.ResponseParams( + sessionTopic = sessionRequest.topic, + jsonRpcResponse = WalletConnectClientData.JsonRpcResponse.JsonRpcError( + sessionRequest.request.id, + WalletConnectClientData.JsonRpcResponse.Error(500, "Kotlin Wallet Error") + ) + ) + + WalletConnectClient.respond(result, object : WalletConnectClientListeners.SessionPayload { + override fun onError(error: Throwable) { + //Error + } + }) + } + + fun sessionUpdate(session: WalletConnectClientData.SettledSession) { + val updateParams = ClientTypes.UpdateParams( + sessionTopic = session.topic, + sessionState = WalletConnectClientData.SessionState(accounts = listOf("eip155:8001:0xa0A6c118b1B25207A8A764E1CAe1635339bedE62")) + ) + + WalletConnectClient.update(updateParams, object : WalletConnectClientListeners.SessionUpdate { + override fun onSuccess(updatedSession: WalletConnectClientData.UpdatedSession) { + viewModelScope.launch { + _eventFlow.emit(UpdateActiveSessions(WalletConnectClient.getListOfSettledSessions(), "Successful session update")) + } + } + + override fun onError(error: Throwable) { + //Error + } + }) + } + + fun sessionUpgrade(session: WalletConnectClientData.SettledSession) { + val permissions = + WalletConnectClientData.SessionPermissions( + blockchain = WalletConnectClientData.Blockchain(chains = listOf("eip155:80001")), + jsonRpc = WalletConnectClientData.Jsonrpc(listOf("eth_sign")) + ) + val upgradeParams = ClientTypes.UpgradeParams(topic = session.topic, permissions = permissions) + WalletConnectClient.upgrade(upgradeParams, object : WalletConnectClientListeners.SessionUpgrade { + override fun onSuccess(upgradedSession: WalletConnectClientData.UpgradedSession) { + viewModelScope.launch { + _eventFlow.emit(UpdateActiveSessions(WalletConnectClient.getListOfSettledSessions(), "Successful session upgrade")) + } + } + + override fun onError(error: Throwable) { + //Error + } + }) + } + + fun sessionPing(session: WalletConnectClientData.SettledSession) { + val pingParams = ClientTypes.PingParams(session.topic) + + WalletConnectClient.ping(pingParams, object : WalletConnectClientListeners.SessionPing { + override fun onSuccess(topic: String) { + viewModelScope.launch { + _eventFlow.emit(PingSuccess) + } + } + + override fun onError(error: Throwable) { + //Error + } + }) + } + + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + viewModelScope.launch { + this@WalletViewModel.proposal = sessionProposal + _eventFlow.emit(ShowSessionProposalDialog(this@WalletViewModel.proposal)) + } + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) { + viewModelScope.launch { + val session = WalletConnectClient.getListOfSettledSessions().find { session -> session.topic == sessionRequest.topic }!! + _eventFlow.emit(ShowSessionRequestDialog(sessionRequest, session)) + } + } + + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) { + viewModelScope.launch { _eventFlow.emit(UpdateActiveSessions(WalletConnectClient.getListOfSettledSessions())) } + } + + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) { + //TODO handle session notification + } +} \ No newline at end of file diff --git a/sample/src/main/java/org/walletconnect/example/wallet/scanner/ScannerFragment.kt b/sample/src/main/java/com/walletconnect/sample/wallet/scanner/ScannerFragment.kt similarity index 96% rename from sample/src/main/java/org/walletconnect/example/wallet/scanner/ScannerFragment.kt rename to sample/src/main/java/com/walletconnect/sample/wallet/scanner/ScannerFragment.kt index 749183050c..5092c94956 100644 --- a/sample/src/main/java/org/walletconnect/example/wallet/scanner/ScannerFragment.kt +++ b/sample/src/main/java/com/walletconnect/sample/wallet/scanner/ScannerFragment.kt @@ -1,4 +1,4 @@ -package org.walletconnect.example.wallet.scanner +package com.walletconnect.sample.wallet.scanner import android.Manifest import android.annotation.SuppressLint @@ -23,9 +23,9 @@ import com.google.mlkit.vision.barcode.BarcodeScanner import com.google.mlkit.vision.barcode.BarcodeScannerOptions import com.google.mlkit.vision.barcode.BarcodeScanning import com.google.mlkit.vision.common.InputImage -import org.walletconnect.example.R -import org.walletconnect.example.databinding.ScannerFragmentBinding -import org.walletconnect.example.wallet.WalletViewModel +import com.walletconnect.sample.R +import com.walletconnect.sample.databinding.ScannerFragmentBinding +import com.walletconnect.sample.wallet.WalletViewModel import java.util.concurrent.Executors class ScannerFragment : Fragment(R.layout.scanner_fragment) { diff --git a/sample/src/main/java/com/walletconnect/sample/wallet/ui/LabeledTextView.kt b/sample/src/main/java/com/walletconnect/sample/wallet/ui/LabeledTextView.kt new file mode 100644 index 0000000000..5a2b50b07d --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/wallet/ui/LabeledTextView.kt @@ -0,0 +1,24 @@ +package com.walletconnect.sample.wallet.ui + +import android.content.Context +import android.text.method.ScrollingMovementMethod +import android.util.AttributeSet +import android.widget.LinearLayout +import com.walletconnect.sample.R +import com.walletconnect.sample.databinding.LabeledTextViewBinding + +class LabeledTextView(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { + + private var binding = LabeledTextViewBinding.bind(inflate(context, R.layout.labeled_text_view, this)) + + init { + binding.body.movementMethod = ScrollingMovementMethod() + } + + fun setTitleAndBody(titleText: String, bodyText: String) { + binding.apply { + title.text = titleText + body.text = bodyText + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/com/walletconnect/sample/wallet/ui/SessionsAdapter.kt b/sample/src/main/java/com/walletconnect/sample/wallet/ui/SessionsAdapter.kt new file mode 100644 index 0000000000..1f702df9ef --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/wallet/ui/SessionsAdapter.kt @@ -0,0 +1,67 @@ +package com.walletconnect.sample.wallet.ui + +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.PopupMenu +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.walletconnect.sample.R +import com.walletconnect.sample.databinding.SessionItemBinding +import com.walletconnect.sample.wallet.SessionActionListener +import com.walletconnect.walletconnectv2.client.WalletConnectClientData + +class SessionsAdapter(private val listener: SessionActionListener) : RecyclerView.Adapter() { + private var sessions: List = listOf() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SessionViewHolder = + SessionViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.session_item, parent, false), listener) + + override fun onBindViewHolder(holder: SessionViewHolder, position: Int) { + holder.bind(sessions[position]) + } + + override fun getItemCount(): Int = sessions.size + + fun updateList(sessions: List) { + this.sessions = sessions + notifyDataSetChanged() + } + + + inner class SessionViewHolder(private val view: View, private val listener: SessionActionListener) : RecyclerView.ViewHolder(view) { + + private val binding = SessionItemBinding.bind(view) + + fun bind(session: WalletConnectClientData.SettledSession) = with(binding) { + + view.setOnClickListener { + listener.onSessionsDetails(session) + } + + Glide.with(view.context) + .load(Uri.parse(session.peerAppMetaData?.icons?.first())) + .into(icon) + + name.text = session.peerAppMetaData?.name + uri.text = session.peerAppMetaData?.url + + menu.setOnClickListener { + with(PopupMenu(view.context, menu)) { + menuInflater.inflate(R.menu.session_menu, menu) + setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.disconnect -> listener.onDisconnect(session) + R.id.update -> listener.onUpdate(session) + R.id.upgrade -> listener.onUpgrade(session) + R.id.ping -> listener.onPing(session) + } + true + } + show() + } + } + } + } +} diff --git a/sample/src/main/java/com/walletconnect/sample/wallet/ui/WalletUiState.kt b/sample/src/main/java/com/walletconnect/sample/wallet/ui/WalletUiState.kt new file mode 100644 index 0000000000..47e9e1525a --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/wallet/ui/WalletUiState.kt @@ -0,0 +1,15 @@ +package com.walletconnect.sample.wallet.ui + +import com.walletconnect.walletconnectv2.client.WalletConnectClientData + +sealed class WalletUiEvent +data class InitSessionsList(val sessions: List) : WalletUiEvent() +data class ShowSessionProposalDialog(val proposal: WalletConnectClientData.SessionProposal) : WalletUiEvent() +data class UpdateActiveSessions(val sessions: List, val message: String? = null) : WalletUiEvent() +data class ShowSessionRequestDialog( + val sessionRequest: WalletConnectClientData.SessionRequest, + val session: WalletConnectClientData.SettledSession +) : WalletUiEvent() + +object PingSuccess : WalletUiEvent() +object RejectSession : WalletUiEvent() \ No newline at end of file diff --git a/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionDetailsDialog.kt b/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionDetailsDialog.kt new file mode 100644 index 0000000000..89d626ba6e --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionDetailsDialog.kt @@ -0,0 +1,43 @@ +package com.walletconnect.sample.wallet.ui.dialog + +import android.content.Context +import android.net.Uri +import com.bumptech.glide.Glide +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.walletconnect.sample.databinding.SessionDetailsDialogBinding +import com.walletconnect.walletconnectv2.client.WalletConnectClientData + +class SessionDetailsDialog(context: Context, private val session: WalletConnectClientData.SettledSession) : BottomSheetDialog(context) { + + private val binding = SessionDetailsDialogBinding.inflate(layoutInflater) + + init { + setContentView(binding.root) + setContent() + } + + private fun setContent() = with(binding) { + Glide.with(context) + .load(Uri.parse(session.peerAppMetaData?.icons?.first().toString())) + .into(icon) + name.text = session.peerAppMetaData?.name + uri.text = session.peerAppMetaData?.url + description.text = session.peerAppMetaData?.description + + var accountsString = "" + session.accounts.forEach { + accountsString += "$it\n" + } + accounts.text = accountsString + + var methodsString = "" + session.permissions.jsonRpc.methods.forEach { + methodsString += "$it\n" + } + methods.text = methodsString + + close.setOnClickListener { + dismiss() + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/org/walletconnect/example/wallet/ui/SessionProposalDialog.kt b/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionProposalDialog.kt similarity index 72% rename from sample/src/main/java/org/walletconnect/example/wallet/ui/SessionProposalDialog.kt rename to sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionProposalDialog.kt index cff16ff4d5..9de1a78987 100644 --- a/sample/src/main/java/org/walletconnect/example/wallet/ui/SessionProposalDialog.kt +++ b/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionProposalDialog.kt @@ -1,17 +1,17 @@ -package org.walletconnect.example.wallet.ui +package com.walletconnect.sample.wallet.ui.dialog import android.content.Context import android.net.Uri import com.bumptech.glide.Glide import com.google.android.material.bottomsheet.BottomSheetDialog -import org.walletconnect.example.databinding.SessionProposalDialogBinding -import org.walletconnect.walletconnectv2.client.SessionProposal +import com.walletconnect.sample.databinding.SessionProposalDialogBinding +import com.walletconnect.walletconnectv2.client.WalletConnectClientData class SessionProposalDialog( context: Context, val approve: () -> Unit, val reject: () -> Unit, - private val proposal: SessionProposal + private val proposal: WalletConnectClientData.SessionProposal ) : BottomSheetDialog(context) { private val binding = SessionProposalDialogBinding.inflate(layoutInflater) @@ -23,10 +23,10 @@ class SessionProposalDialog( private fun setContent() = with(binding) { Glide.with(context) - .load(Uri.parse(proposal.icon.first().toString())) + .load(Uri.parse(proposal.icons.first().toString())) .into(icon) name.text = proposal.name - uri.text = proposal.dappUrl + uri.text = proposal.url description.text = proposal.description var chainsString = "" proposal.chains.forEach { @@ -41,10 +41,12 @@ class SessionProposalDialog( methods.text = methodsString approve.setOnClickListener { + dismiss() approve() } reject.setOnClickListener { + dismiss() reject() } } diff --git a/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionRequestDialog.kt b/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionRequestDialog.kt new file mode 100644 index 0000000000..d43b3343aa --- /dev/null +++ b/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/SessionRequestDialog.kt @@ -0,0 +1,46 @@ +package com.walletconnect.sample.wallet.ui.dialog + +import android.content.Context +import android.net.Uri +import com.bumptech.glide.Glide +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.walletconnect.sample.databinding.SessionRequestDialogBinding +import com.walletconnect.walletconnectv2.client.WalletConnectClientData + +class SessionRequestDialog( + context: Context, + val approve: (sessionRequest: WalletConnectClientData.SessionRequest) -> Unit, + val reject: (sessionRequest: WalletConnectClientData.SessionRequest) -> Unit, + private val sessionRequest: WalletConnectClientData.SessionRequest, + private val session: WalletConnectClientData.SettledSession +) : BottomSheetDialog(context) { + + private val binding = SessionRequestDialogBinding.inflate(layoutInflater) + + init { + setCancelable(false) + setContentView(binding.root) + setContent() + } + + private fun setContent() = with(binding) { + Glide.with(context) + .load(Uri.parse(session.peerAppMetaData?.icons?.first().toString())) + .into(icon) + name.text = session.peerAppMetaData?.name + uri.text = session.peerAppMetaData?.url + message.setTitleAndBody("Params", sessionRequest.request.params) + chains.text = sessionRequest.chainId + methods.text = sessionRequest.request.method + + approve.setOnClickListener { + dismiss() + approve(sessionRequest) + } + + reject.setOnClickListener { + dismiss() + reject(sessionRequest) + } + } +} \ No newline at end of file diff --git a/sample/src/main/java/org/walletconnect/example/wallet/ui/UrlDialog.kt b/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/UrlDialog.kt similarity index 52% rename from sample/src/main/java/org/walletconnect/example/wallet/ui/UrlDialog.kt rename to sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/UrlDialog.kt index a305269762..1e07338400 100644 --- a/sample/src/main/java/org/walletconnect/example/wallet/ui/UrlDialog.kt +++ b/sample/src/main/java/com/walletconnect/sample/wallet/ui/dialog/UrlDialog.kt @@ -1,17 +1,17 @@ -package org.walletconnect.example.wallet.ui +package com.walletconnect.sample.wallet.ui.dialog import android.content.Context import com.google.android.material.bottomsheet.BottomSheetDialog -import org.walletconnect.example.databinding.UrlDialogBinding +import com.walletconnect.sample.databinding.UrlDialogBinding -class UrlDialog(context: Context, val approve: (url: String) -> Unit) : BottomSheetDialog(context) { +class UrlDialog(context: Context, val pair: (url: String) -> Unit) : BottomSheetDialog(context) { private val binding = UrlDialogBinding.inflate(layoutInflater) init { setContentView(binding.root) binding.ok.setOnClickListener { - approve(binding.pasteUri.text.toString()) + pair(binding.pasteUri.text.toString()) dismiss() } } diff --git a/sample/src/main/java/org/walletconnect/example/SampleApplication.kt b/sample/src/main/java/org/walletconnect/example/SampleApplication.kt deleted file mode 100644 index 4ce1c353ac..0000000000 --- a/sample/src/main/java/org/walletconnect/example/SampleApplication.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.walletconnect.example - -import android.app.Application -import org.walletconnect.walletconnectv2.WalletConnectClient -import org.walletconnect.walletconnectv2.client.ClientTypes - -class SampleApplication : Application() { - - override fun onCreate() { - super.onCreate() - - val initParams = ClientTypes.InitialParams(application = this, hostName = "relay.walletconnect.org") - WalletConnectClient.initialize(initParams) - } -} \ No newline at end of file diff --git a/sample/src/main/java/org/walletconnect/example/wallet/WalletFragment.kt b/sample/src/main/java/org/walletconnect/example/wallet/WalletFragment.kt deleted file mode 100644 index ef856f3685..0000000000 --- a/sample/src/main/java/org/walletconnect/example/wallet/WalletFragment.kt +++ /dev/null @@ -1,55 +0,0 @@ -package org.walletconnect.example.wallet - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.navigation.fragment.findNavController -import org.walletconnect.example.R -import org.walletconnect.example.databinding.WalletFragmentBinding -import org.walletconnect.example.wallet.ui.* - -class WalletFragment : Fragment(R.layout.wallet_fragment) { - private val viewModel: WalletViewModel by activityViewModels() - private lateinit var binding: WalletFragmentBinding - private val sessionAdapter = SessionsAdapter() - private var proposalDialog: SessionProposalDialog? = null - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding = WalletFragmentBinding.bind(view) - setupToolbar() - binding.sessions.adapter = sessionAdapter - sessionAdapter.updateList(viewModel.activeSessions) - viewModel.eventFlow.observe(viewLifecycleOwner, { event -> - when (event) { - is ShowSessionProposalDialog -> { - proposalDialog = SessionProposalDialog(requireContext(), viewModel::approve, viewModel::reject, event.proposal) - proposalDialog?.show() - } - is UpdateActiveSessions -> { - proposalDialog?.dismiss() - sessionAdapter.updateList(event.sessions) - } - is RejectSession -> proposalDialog?.dismiss() - } - }) - } - - private fun setupToolbar() { - binding.walletToolbar.title = getString(R.string.app_name) - binding.walletToolbar.setOnMenuItemClickListener { item -> - when (item.itemId) { - R.id.qrCodeScanner -> { - findNavController().navigate(R.id.action_walletFragment_to_scannerFragment) - true - } - R.id.pasteUri -> { - UrlDialog(requireContext(), approve = viewModel::pair).show() - true - } - else -> false - } - } - } -} \ No newline at end of file diff --git a/sample/src/main/java/org/walletconnect/example/wallet/WalletViewModel.kt b/sample/src/main/java/org/walletconnect/example/wallet/WalletViewModel.kt deleted file mode 100644 index 9a38c63b69..0000000000 --- a/sample/src/main/java/org/walletconnect/example/wallet/WalletViewModel.kt +++ /dev/null @@ -1,66 +0,0 @@ -package org.walletconnect.example.wallet - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.launch -import org.walletconnect.example.wallet.ui.* -import org.walletconnect.walletconnectv2.WalletConnectClient -import org.walletconnect.walletconnectv2.client.ClientTypes -import org.walletconnect.walletconnectv2.client.SessionProposal - -class WalletViewModel : ViewModel() { - private var _eventFlow = MutableSharedFlow() - val eventFlow = _eventFlow.asLiveData() - - val activeSessions: MutableList = mutableListOf() - lateinit var sessionProposal: SessionProposal - - fun pair(uri: String) { - val pairParams = ClientTypes.PairParams(uri.trim()) - - WalletConnectClient.pair(pairParams) { sessionProposal -> - viewModelScope.launch { - this@WalletViewModel.sessionProposal = sessionProposal - _eventFlow.emit(ShowSessionProposalDialog(sessionProposal)) - } - } - } - - fun approve() { - val session = Session( - name = sessionProposal.name, - uri = sessionProposal.dappUrl, - icon = sessionProposal.icon.first().toString() - ) - - activeSessions += session - - val proposerPublicKey: String = sessionProposal.proposerPublicKey - val proposalTtl: Long = sessionProposal.ttl - val proposalTopic: String = sessionProposal.topic - val accounts = sessionProposal.chains.map { chainId -> - "$chainId:0x022c0c42a80bd19EA4cF0F94c4F9F96645759716" - } - val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(accounts, proposerPublicKey, proposalTtl, proposalTopic) - - WalletConnectClient.approve(approveParams) - - viewModelScope.launch { - _eventFlow.emit(UpdateActiveSessions(activeSessions)) - } - } - - fun reject() { - val rejectionReason = "Reject Session" - val proposalTopic: String = sessionProposal.topic - val rejectParams: ClientTypes.RejectParams = ClientTypes.RejectParams(rejectionReason, proposalTopic) - - WalletConnectClient.reject(rejectParams) - - viewModelScope.launch { - _eventFlow.emit(RejectSession) - } - } -} \ No newline at end of file diff --git a/sample/src/main/java/org/walletconnect/example/wallet/ui/SessionsAdapter.kt b/sample/src/main/java/org/walletconnect/example/wallet/ui/SessionsAdapter.kt deleted file mode 100644 index 8c15cb8e35..0000000000 --- a/sample/src/main/java/org/walletconnect/example/wallet/ui/SessionsAdapter.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.walletconnect.example.wallet.ui - -import android.net.Uri -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.Glide -import org.walletconnect.example.R -import org.walletconnect.example.databinding.SessionItemBinding - -class SessionsAdapter : RecyclerView.Adapter() { - var sessions: List = listOf() - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SessionViewHolder = - SessionViewHolder( - LayoutInflater.from(parent.context).inflate(R.layout.session_item, parent, false) - ) - - override fun onBindViewHolder(holder: SessionViewHolder, position: Int) { - holder.bind(sessions[position]) - } - - override fun getItemCount(): Int = sessions.size - - fun updateList(sessions: List) { - this.sessions = sessions - notifyDataSetChanged() - } -} - -class SessionViewHolder(val view: View) : RecyclerView.ViewHolder(view) { - - private val binding = SessionItemBinding.bind(view) - - fun bind(session: Session) = with(binding) { - Glide.with(view.context) - .load(Uri.parse(session.icon)) - .into(icon) - name.text = session.name - uri.text = session.uri - } -} diff --git a/sample/src/main/java/org/walletconnect/example/wallet/ui/WalletUiState.kt b/sample/src/main/java/org/walletconnect/example/wallet/ui/WalletUiState.kt deleted file mode 100644 index df953014fe..0000000000 --- a/sample/src/main/java/org/walletconnect/example/wallet/ui/WalletUiState.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.walletconnect.example.wallet.ui - -import org.walletconnect.walletconnectv2.client.SessionProposal - -sealed class WalletUiEvent -data class ShowSessionProposalDialog(val proposal: SessionProposal) : WalletUiEvent() -data class UpdateActiveSessions(val sessions: List) : WalletUiEvent() -object RejectSession : WalletUiEvent() - -data class Session( - var icon: String = "", - var name: String = "", - var uri: String = "" -) \ No newline at end of file diff --git a/sample/src/main/res/drawable/ic_menu.xml b/sample/src/main/res/drawable/ic_menu.xml new file mode 100644 index 0000000000..73f3be3d2f --- /dev/null +++ b/sample/src/main/res/drawable/ic_menu.xml @@ -0,0 +1,10 @@ + + + diff --git a/sample/src/main/res/drawable/rounded_frame.xml b/sample/src/main/res/drawable/rounded_frame.xml new file mode 100644 index 0000000000..6cd769c1a7 --- /dev/null +++ b/sample/src/main/res/drawable/rounded_frame.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/labeled_text_view.xml b/sample/src/main/res/layout/labeled_text_view.xml new file mode 100644 index 0000000000..5a3f800830 --- /dev/null +++ b/sample/src/main/res/layout/labeled_text_view.xml @@ -0,0 +1,36 @@ + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/session_details_dialog.xml b/sample/src/main/res/layout/session_details_dialog.xml new file mode 100644 index 0000000000..bd03e8a477 --- /dev/null +++ b/sample/src/main/res/layout/session_details_dialog.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/session_item.xml b/sample/src/main/res/layout/session_item.xml index 5daac365af..a34456bf0e 100644 --- a/sample/src/main/res/layout/session_item.xml +++ b/sample/src/main/res/layout/session_item.xml @@ -41,4 +41,14 @@ app:layout_constraintLeft_toRightOf="@+id/icon" app:layout_constraintTop_toBottomOf="@id/name" tools:text="org.name.vom" /> + + \ No newline at end of file diff --git a/sample/src/main/res/layout/session_request_dialog.xml b/sample/src/main/res/layout/session_request_dialog.xml new file mode 100644 index 0000000000..35d7447e66 --- /dev/null +++ b/sample/src/main/res/layout/session_request_dialog.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/menu/session_menu.xml b/sample/src/main/res/menu/session_menu.xml new file mode 100644 index 0000000000..58f5935c94 --- /dev/null +++ b/sample/src/main/res/menu/session_menu.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/navigation/nav_graph.xml b/sample/src/main/res/navigation/nav_graph.xml index c4bb189827..8b1fe5c70e 100644 --- a/sample/src/main/res/navigation/nav_graph.xml +++ b/sample/src/main/res/navigation/nav_graph.xml @@ -6,7 +6,7 @@ app:startDestination="@id/walletFragment"> Approve
Paste Uri Paste Uri... + Disconnect \ No newline at end of file diff --git a/sample/src/test/java/org/walletconnect/example/ExampleUnitTest.kt b/sample/src/test/java/org/walletconnect/example/ExampleUnitTest.kt deleted file mode 100644 index 94ef09d42c..0000000000 --- a/sample/src/test/java/org/walletconnect/example/ExampleUnitTest.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.walletconnect.example - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - -} \ No newline at end of file diff --git a/walletconnectv2/build.gradle.kts b/walletconnectv2/build.gradle.kts index 4430be5190..97019ceea2 100644 --- a/walletconnectv2/build.gradle.kts +++ b/walletconnectv2/build.gradle.kts @@ -4,6 +4,7 @@ plugins { id("com.android.library") kotlin("android") kotlin("kapt") + id("com.squareup.sqldelight") `maven-publish` } @@ -15,10 +16,11 @@ android { compileSdk = 30 defaultConfig { - minSdk = 21 + minSdk = 23 targetSdk = 30 - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "com.walletconnect.walletconnectv2.WCTestRunner" + testInstrumentationRunnerArguments += mutableMapOf("runnerBuilder" to "de.mannodermaus.junit5.AndroidJUnit5Builder") } buildTypes { @@ -34,9 +36,21 @@ android { sourceCompatibility = jvmVersion targetCompatibility = jvmVersion } + kotlinOptions { jvmTarget = jvmVersion.toString() } + + testOptions.unitTests.isIncludeAndroidResources = true + + packagingOptions { + resources.excludes += setOf( + "META-INF/LICENSE.md", + "META-INF/LICENSE-notice.md", + "META-INF/AL2.0", + "META-INF/LGPL2.1" + ) + } } kotlin { @@ -52,11 +66,15 @@ kotlin { dependencies { okhttp() - lazySodium() + bouncyCastle() coroutines() moshi() scarlet() + sqlDelight() + security() + jUnit5() + robolectric() mockk() timber() } diff --git a/walletconnectv2/src/androidTest/AndroidManifest.xml b/walletconnectv2/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..4f0638e424 --- /dev/null +++ b/walletconnectv2/src/androidTest/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WCIntegrationActivityScenarioRule.kt b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WCIntegrationActivityScenarioRule.kt new file mode 100644 index 0000000000..1f27f9ed16 --- /dev/null +++ b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WCIntegrationActivityScenarioRule.kt @@ -0,0 +1,47 @@ +package com.walletconnect.walletconnectv2 + +import androidx.lifecycle.Lifecycle +import androidx.test.core.app.ActivityScenario +import org.junit.Assert +import org.junit.rules.ExternalResource +import com.walletconnect.walletconnectv2.utils.IntegrationTestActivity +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +// Source: https://gist.github.com/zawadz88/f057c70d3061454207ccd56e0add81c6#file-lazyactivityscenariorule-kt +class WCIntegrationActivityScenarioRule : ExternalResource() { + private var scenario: ActivityScenario? = null + private var scenarioLaunched: Boolean = false + private val latch = CountDownLatch(1) + + override fun before() { + } + + override fun after() { + scenario?.close() + } + + fun launch(timeoutMinutes: Long = 1, testCodeBlock: () -> Unit) { + require(!scenarioLaunched) { "Scenario has already been launched!" } + + scenario = ActivityScenario.launch(IntegrationTestActivity::class.java) + scenarioLaunched = true + + scenario?.moveToState(Lifecycle.State.RESUMED) + assert(scenario?.state?.isAtLeast(Lifecycle.State.RESUMED) == true) + + testCodeBlock() + + try { + latch.await(timeoutMinutes, TimeUnit.MINUTES) + } catch (exception: InterruptedException) { + Assert.fail(exception.stackTraceToString()) + } catch (exception: IllegalArgumentException) { + Assert.fail(exception.stackTraceToString()) + } + } + + fun close() { + latch.countDown() + } +} \ No newline at end of file diff --git a/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WCTestRunner.kt b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WCTestRunner.kt new file mode 100644 index 0000000000..0b5f9498f4 --- /dev/null +++ b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WCTestRunner.kt @@ -0,0 +1,13 @@ +package com.walletconnect.walletconnectv2 + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import com.walletconnect.walletconnectv2.utils.IntegrationTestApplication + +class WCTestRunner: AndroidJUnitRunner() { + + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + return super.newApplication(cl, IntegrationTestApplication::class.java.name, context) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WalletConnectClientIntegrationAndroidTest.kt b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WalletConnectClientIntegrationAndroidTest.kt new file mode 100644 index 0000000000..08d4b5e4c3 --- /dev/null +++ b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/WalletConnectClientIntegrationAndroidTest.kt @@ -0,0 +1,502 @@ +package com.walletconnect.walletconnectv2 + +import androidx.test.core.app.ApplicationProvider +import org.junit.Rule +import org.junit.Test +import com.walletconnect.walletconnectv2.client.ClientTypes +import com.walletconnect.walletconnectv2.client.WalletConnectClientData +import com.walletconnect.walletconnectv2.client.WalletConnectClientListener +import com.walletconnect.walletconnectv2.client.WalletConnectClientListeners +import com.walletconnect.walletconnectv2.common.AppMetaData +import com.walletconnect.walletconnectv2.utils.IntegrationTestApplication + +class WalletConnectClientIntegrationAndroidTest { + @get:Rule + val activityRule = WCIntegrationActivityScenarioRule() + private val app = ApplicationProvider.getApplicationContext() + + private val metadata = AppMetaData( + name = "Kotlin Wallet", + description = "Wallet description", + url = "example.wallet", + icons = listOf("https://gblobscdn.gitbook.com/spaces%2F-LJJeCjcLrr53DcT1Ml7%2Favatar.png?alt=media") + ) + + @Test + fun responderApprovePairingAndGetSessionProposalTest() { + activityRule.launch { + val initParams = ClientTypes.InitialParams(application = app, hostName = "relay.walletconnect.org", metadata = metadata) + WalletConnectClient.initialize(initParams) + val uri = + "wc:4f47abd615d5b56941989e120f108c2f338801ce16ee902237654b8c1970e8a2@2?controller=false&publicKey=2d573da1d2b8dbe3dcdb6ce7de47ce44b18fb8ec5ddc9d3f412ab4a718fff93c&relay=%7B%22protocol%22%3A%22waku%22%7D" + val pairingParams = ClientTypes.PairParams(uri) + + val listener = object : WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + assert(true) + activityRule.close() + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) {} + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {} + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {} + } + + WalletConnectClient.setWalletConnectListener(listener) + WalletConnectClient.pair(pairingParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + } + + @Test + fun responderSessionApproveTest() { + activityRule.launch { + val initParams = ClientTypes.InitialParams(application = app, hostName = "relay.walletconnect.org", metadata = metadata) + WalletConnectClient.initialize(initParams) + + val uri = + "wc:76a4fd7ab4015aa22ad77bfa0cd0bc563047fd3d92ad5285db71e80cea68f9ca@2?controller=false&publicKey=7a6875ec8512a8d77be1ddd23797a08d078627c0f85887bf0452be97b7390e34&relay=%7B%22protocol%22%3A%22waku%22%7D" + val pairingParams = ClientTypes.PairParams(uri) + val listener = object : WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + assert(true) + val accounts = sessionProposal.chains.map { chainId -> "$chainId:0x022c0c42a80bd19EA4cF0F94c4F9F96645759716" } + val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts) + WalletConnectClient.approve(approveParams, object : WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + assert(true) + activityRule.close() + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) {} + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {} + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {} + } + + WalletConnectClient.setWalletConnectListener(listener) + WalletConnectClient.pair(pairingParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + } + + @Test + fun responderUpgradeSessionPermissionsTest() { + activityRule.launch { + val initParams = ClientTypes.InitialParams(application = app, hostName = "relay.walletconnect.org", metadata = metadata) + WalletConnectClient.initialize(initParams) + + val uri = + "wc:a26b323e1ad907d7c68264dbf9d5ccbd2077adae3195d23d7b052116ffcd4736@2?controller=false&publicKey=b9577a879cb2fea465ff945e409790b5996cc21dab10db48ceda23cfa3baab37&relay=%7B%22protocol%22%3A%22waku%22%7D" + val pairingParams = ClientTypes.PairParams(uri) + + val listener = object : WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + assert(true) + + val accounts = sessionProposal.chains.map { chainId -> "$chainId:0x022c0c42a80bd19EA4cF0F94c4F9F96645759716" } + val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts) + + WalletConnectClient.approve(approveParams, object : WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + + val permissions = + WalletConnectClientData.SessionPermissions( + blockchain = WalletConnectClientData.Blockchain(chains = listOf("eip155:80001")), + jsonRpc = WalletConnectClientData.Jsonrpc(listOf("eth_sign")) + ) + val upgradeParams = ClientTypes.UpgradeParams(settledSession.topic, permissions) + WalletConnectClient.upgrade(upgradeParams, object : WalletConnectClientListeners.SessionUpgrade { + override fun onSuccess(upgradedSession: WalletConnectClientData.UpgradedSession) { + assert(true) + activityRule.close() + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) {} + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {} + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {} + } + + WalletConnectClient.setWalletConnectListener(listener) + WalletConnectClient.pair(pairingParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + } + + @Test + fun responderAcceptRequestAndSendResponseTest() { + activityRule.launch { + val initParams = ClientTypes.InitialParams(application = app, hostName = "relay.walletconnect.org", metadata = metadata) + WalletConnectClient.initialize(initParams) + + val uri = + "wc:a436606363ab68232f14d46899397d2d765488a1d5b599922a5e11f3826b44eb@2?controller=false&publicKey=6868953b0b4fdbf203902dd2ea2c982a106c5656879b18df815343fe5e609a6d&relay=%7B%22protocol%22%3A%22waku%22%7D" + val pairingParams = ClientTypes.PairParams(uri) + + + val listener = object : WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + assert(true) + val accounts = sessionProposal.chains.map { chainId -> "$chainId:0xa0A6c118b1B25207A8A764E1CAe1635339bedE62" } + val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts) + + WalletConnectClient.approve(approveParams, object : WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) { + val result = ClientTypes.ResponseParams( + sessionTopic = sessionRequest.topic, + jsonRpcResponse = WalletConnectClientData.JsonRpcResponse.JsonRpcResult( + sessionRequest.request.id, + "0xa3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a12d887fd3b17bfdce3481f10bea41f45ba9f709d39ce8325427b57afcfc994cee1b" + ) + ) + + WalletConnectClient.respond(result, object : WalletConnectClientListeners.SessionPayload { + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + + } + + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {} + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {} + } + + WalletConnectClient.setWalletConnectListener(listener) + WalletConnectClient.pair(pairingParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + } + + @Test + fun responderAcceptRequestAndSendErrorTest() { + activityRule.launch { + val initParams = ClientTypes.InitialParams(application = app, hostName = "relay.walletconnect.org", metadata = metadata) + WalletConnectClient.initialize(initParams) + + val uri = + "wc:5435739a2365dd46bbbb2543abbad1964bb702f622428f63d0d4257ddd7df7b7@2?controller=false&publicKey=76464aa17766b58a335e8ee6a96d8be7e1bbfcd81307c57e81b0b6cd54639765&relay=%7B%22protocol%22%3A%22waku%22%7D" + val pairingParams = ClientTypes.PairParams(uri) + + + val listener = object : WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + assert(true) + val accounts = sessionProposal.chains.map { chainId -> "$chainId:0xa0A6c118b1B25207A8A764E1CAe1635339bedE62" } + val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts) + + WalletConnectClient.approve(approveParams, object : WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) { + val result = ClientTypes.ResponseParams( + sessionTopic = sessionRequest.topic, + jsonRpcResponse = WalletConnectClientData.JsonRpcResponse.JsonRpcError( + sessionRequest.request.id, + WalletConnectClientData.JsonRpcResponse.Error(500, "Kotlin Wallet Error") + ) + ) + + WalletConnectClient.respond(result, object : WalletConnectClientListeners.SessionPayload { + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {} + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {} + } + + WalletConnectClient.setWalletConnectListener(listener) + WalletConnectClient.pair(pairingParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + } + + @Test + fun responderSessionUpdateTest() { + activityRule.launch { + val initParams = ClientTypes.InitialParams(application = app, hostName = "relay.walletconnect.org", metadata = metadata) + WalletConnectClient.initialize(initParams) + + val uri = + "wc:7518ca65d85b3084d3b5f5fb223a7cd902c8bb5faca80fbe3e4f74f936eecd20@2?controller=false&publicKey=55fa723c020e8b3d3f7cc01c0d7f7eaf246fce2203e8f2f32580f2d947312a09&relay=%7B%22protocol%22%3A%22waku%22%7D" + val pairingParams = ClientTypes.PairParams(uri) + + + val listener = object : WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + assert(true) + val accounts = sessionProposal.chains.map { chainId -> "$chainId:0xa0A6c118b1B25207A8A764E1CAe1635339bedE62" } + val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts) + + WalletConnectClient.approve(approveParams, object : WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + + val updateParams = ClientTypes.UpdateParams( + settledSession.topic, + WalletConnectClientData.SessionState(accounts = listOf("eip155:8001:0x022c0c42a80bd19EA4cF0F94c4F9F96645759716")) + ) + + WalletConnectClient.update(updateParams, object : WalletConnectClientListeners.SessionUpdate { + override fun onSuccess(updatedSession: WalletConnectClientData.UpdatedSession) { + assert(true) + activityRule.close() + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) {} + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {} + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {} + } + + WalletConnectClient.setWalletConnectListener(listener) + WalletConnectClient.pair(pairingParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + } + + @Test + fun responderSendSessionPingTest() { + activityRule.launch { + val initParams = ClientTypes.InitialParams(application = app, hostName = "relay.walletconnect.org", metadata = metadata) + WalletConnectClient.initialize(initParams) + + val uri = + "wc:776558f58c4a41273d6a7dee4404eb58bed4b42949370e06680992f4916ca600@2?controller=false&publicKey=fe0ae6439d3b3fa8aaf6e478bd3e6d4528ec21cb595bb73f6c9c62b1e5135b23&relay=%7B%22protocol%22%3A%22waku%22%7D" + val pairingParams = ClientTypes.PairParams(uri) + + + val listener = object : WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + assert(true) + val accounts = sessionProposal.chains.map { chainId -> "$chainId:0xa0A6c118b1B25207A8A764E1CAe1635339bedE62" } + val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts) + + WalletConnectClient.approve(approveParams, object : WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + + val pingParams = ClientTypes.PingParams(settledSession.topic) + + WalletConnectClient.ping(pingParams, object : WalletConnectClientListeners.SessionPing { + override fun onSuccess(topic: String) { + assert(true) + activityRule.close() + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) {} + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {} + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {} + } + + WalletConnectClient.setWalletConnectListener(listener) + WalletConnectClient.pair(pairingParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + } + + @Test + fun responderSendNotificationTest() { + activityRule.launch { + val initParams = ClientTypes.InitialParams(application = app, hostName = "relay.walletconnect.org", metadata = metadata) + WalletConnectClient.initialize(initParams) + + + + val uri = + "wc:8d45a8b64d4b921ee8608053ebbbea7a52d8c59ded79f379a868f524c868789f@2?controller=false&publicKey=8d18b02dbbd8c29133255a847061af36a7673ebdcdbf0a05aaac3a3ef7391703&relay=%7B%22protocol%22%3A%22waku%22%7D" + val pairingParams = ClientTypes.PairParams(uri) + + + + val listener = object : WalletConnectClientListener { + override fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) { + assert(true) + val accounts = sessionProposal.chains.map { chainId -> "$chainId:0xa0A6c118b1B25207A8A764E1CAe1635339bedE62" } + val approveParams: ClientTypes.ApproveParams = ClientTypes.ApproveParams(sessionProposal, accounts) + + WalletConnectClient.approve(approveParams, object : WalletConnectClientListeners.SessionApprove { + override fun onSuccess(settledSession: WalletConnectClientData.SettledSession) { + + + val notificationParams = + ClientTypes.NotificationParams( + settledSession.topic, + WalletConnectClientData.Notification("type", "test") + ) + + WalletConnectClient.notify(notificationParams, object : WalletConnectClientListeners.Notification { + override fun onSuccess(topic: String) { + assert(true) + activityRule.close() + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + }) + } + + override fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) {} + override fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) {} + override fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) {} + } + + WalletConnectClient.setWalletConnectListener(listener) + WalletConnectClient.pair(pairingParams, object : WalletConnectClientListeners.Pairing { + override fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) { + assert(true) + } + + override fun onError(error: Throwable) { + assert(false) + activityRule.close() + } + + }) + } + } +} \ No newline at end of file diff --git a/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/utils/IntegrationTestActivity.kt b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/utils/IntegrationTestActivity.kt new file mode 100644 index 0000000000..8c94038a3f --- /dev/null +++ b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/utils/IntegrationTestActivity.kt @@ -0,0 +1,14 @@ +package com.walletconnect.walletconnectv2.utils + +import android.os.Bundle +import android.widget.LinearLayout +import androidx.appcompat.app.AppCompatActivity + +class IntegrationTestActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(LinearLayout(this)) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/utils/IntegrationTestApplication.kt b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/utils/IntegrationTestApplication.kt new file mode 100644 index 0000000000..ac268d36a7 --- /dev/null +++ b/walletconnectv2/src/androidTest/kotlin/com/walletconnect/walletconnectv2/utils/IntegrationTestApplication.kt @@ -0,0 +1,5 @@ +package com.walletconnect.walletconnectv2.utils + +import android.app.Application + +class IntegrationTestApplication : Application() \ No newline at end of file diff --git a/walletconnectv2/src/main/AndroidManifest.xml b/walletconnectv2/src/main/AndroidManifest.xml index 413f4299b2..efba66c486 100644 --- a/walletconnectv2/src/main/AndroidManifest.xml +++ b/walletconnectv2/src/main/AndroidManifest.xml @@ -1,5 +1,10 @@ - + + + + \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/ClientParams.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/ClientParams.kt new file mode 100644 index 0000000000..da3e74fe8a --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/ClientParams.kt @@ -0,0 +1,3 @@ +package com.walletconnect.walletconnectv2 + +interface ClientParams \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/WalletConnectClient.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/WalletConnectClient.kt new file mode 100644 index 0000000000..9e2de84427 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/WalletConnectClient.kt @@ -0,0 +1,139 @@ +package com.walletconnect.walletconnectv2 + +import com.walletconnect.walletconnectv2.common.* +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import com.walletconnect.walletconnectv2.client.ClientTypes +import com.walletconnect.walletconnectv2.client.WalletConnectClientData +import com.walletconnect.walletconnectv2.client.WalletConnectClientListener +import com.walletconnect.walletconnectv2.client.WalletConnectClientListeners +import com.walletconnect.walletconnectv2.engine.EngineInteractor +import com.walletconnect.walletconnectv2.engine.model.EngineData +import com.walletconnect.walletconnectv2.engine.sequence.SequenceLifecycle + +object WalletConnectClient { + private val engineInteractor = EngineInteractor() + + fun initialize(initialParams: ClientTypes.InitialParams) = with(initialParams) { + // TODO: pass properties to DI framework + app = application + val engineFactory = com.walletconnect.walletconnectv2.engine.EngineInteractor.EngineFactory(useTls, hostName, projectId, isController, application, metadata) + engineInteractor.initialize(engineFactory) + } + + fun setWalletConnectListener(walletConnectListener: WalletConnectClientListener) { + scope.launch { + engineInteractor.sequenceEvent.collect { event -> + when (event) { + is SequenceLifecycle.OnSessionProposal -> walletConnectListener.onSessionProposal(event.proposal.toClientSessionProposal()) + is SequenceLifecycle.OnSessionRequest -> walletConnectListener.onSessionRequest(event.request.toClientSessionRequest()) + is SequenceLifecycle.OnSessionDeleted -> walletConnectListener.onSessionDelete(event.deletedSession.toClientDeletedSession()) + is SequenceLifecycle.OnSessionNotification -> walletConnectListener.onSessionNotification(event.notification.toClientSessionNotification()) + SequenceLifecycle.Default -> Unit + } + } + } + } + + fun pair( + pairingParams: ClientTypes.PairParams, + listener: WalletConnectClientListeners.Pairing + ) { + engineInteractor.pair( + pairingParams.uri, + { topic -> listener.onSuccess(WalletConnectClientData.SettledPairing(topic)) }, + { error -> listener.onError(error) }) + } + + fun approve( + approveParams: ClientTypes.ApproveParams, + listener: WalletConnectClientListeners.SessionApprove + ) = with(approveParams) { + engineInteractor.approve( + proposal.toEngineSessionProposal(accounts), + { settledSession -> listener.onSuccess(settledSession.toClientSettledSession()) }, + { error -> listener.onError(error) }) + } + + fun reject( + rejectParams: ClientTypes.RejectParams, + listener: WalletConnectClientListeners.SessionReject + ) = with(rejectParams) { + engineInteractor.reject( + rejectionReason, proposalTopic, + { (topic, reason) -> listener.onSuccess(WalletConnectClientData.RejectedSession(topic, reason)) }, + { error -> listener.onError(error) }) + } + + fun respond( + responseParams: ClientTypes.ResponseParams, + listener: WalletConnectClientListeners.SessionPayload + ) = with(responseParams) { + val jsonRpcEngineResponse = when (jsonRpcResponse) { + is WalletConnectClientData.JsonRpcResponse.JsonRpcResult -> jsonRpcResponse.toEngineRpcResult() + is WalletConnectClientData.JsonRpcResponse.JsonRpcError -> jsonRpcResponse.toEngineRpcError() + } + engineInteractor.respondSessionPayload(sessionTopic, jsonRpcEngineResponse) { error -> listener.onError(error) } + } + + fun upgrade( + upgradeParams: ClientTypes.UpgradeParams, + listener: WalletConnectClientListeners.SessionUpgrade + ) = with(upgradeParams) { + engineInteractor.upgrade( + topic, permissions.toEngineSessionPermissions(), + { (topic, permissions) -> listener.onSuccess(WalletConnectClientData.UpgradedSession(topic, permissions.toClientPerms())) }, + { error -> listener.onError(error) }) + } + + fun update( + updateParams: ClientTypes.UpdateParams, + listener: WalletConnectClientListeners.SessionUpdate + ) = with(updateParams) { + engineInteractor.update( + sessionTopic, sessionState.toEngineSessionState(), + { (topic, accounts) -> listener.onSuccess(WalletConnectClientData.UpdatedSession(topic, accounts)) }, + { error -> listener.onError(error) }) + } + + fun ping( + pingParams: ClientTypes.PingParams, + listener: WalletConnectClientListeners.SessionPing + ) { + engineInteractor.ping(pingParams.topic, + { topic -> listener.onSuccess(topic) }, + { error -> listener.onError(error) }) + } + + fun notify( + notificationParams: ClientTypes.NotificationParams, + listener: WalletConnectClientListeners.Notification + ) = with(notificationParams) { + engineInteractor.notify(topic, notification.toEngineNotification(), + { topic -> listener.onSuccess(topic) }, + { error -> listener.onError(error) }) + } + + fun disconnect( + disconnectParams: ClientTypes.DisconnectParams, + listener: WalletConnectClientListeners.SessionDelete + ) = with(disconnectParams) { + engineInteractor.disconnect( + sessionTopic, reason, + { (topic, reason) -> listener.onSuccess(WalletConnectClientData.DeletedSession(topic, reason)) }, + { error -> listener.onError(error) }) + } + + fun getListOfSettledSessions(): List { + return engineInteractor.getListOfSettledSessions().map(EngineData.SettledSession::toClientSettledSession) + } + + fun getListOfPendingSession(): List { + return engineInteractor.getListOfPendingSessions().map(EngineData.SessionProposal::toClientSessionProposal) + } + + fun shutdown() { + scope.cancel() + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/WalletConnectScope.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/WalletConnectScope.kt new file mode 100644 index 0000000000..5d0e29014a --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/WalletConnectScope.kt @@ -0,0 +1,45 @@ +@file:JvmName("WalletConnectScope") + +package com.walletconnect.walletconnectv2 + +import android.app.Application +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import com.tinder.scarlet.utils.getRawType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import org.json.JSONObject +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.SubscriptionId +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.common.network.adapters.* +import com.walletconnect.walletconnectv2.jsonrpc.model.JsonRpcResponse + +//TODO add job cancellation to avoid memory leaks +internal lateinit var app: Application +private val job = SupervisorJob() +internal val scope = CoroutineScope(job + Dispatchers.IO) + +private val polymorphicJsonAdapterFactory: PolymorphicJsonAdapterFactory = + PolymorphicJsonAdapterFactory.of(JsonRpcResponse::class.java, "type") + .withSubtype(JsonRpcResponse.JsonRpcResult::class.java, "result") + .withSubtype(JsonRpcResponse.JsonRpcError::class.java, "error") + +//TODO move to the DI framework +val moshi: Moshi = Moshi.Builder() + .addLast { type, _, _ -> + when (type.getRawType().name) { + Expiry::class.qualifiedName -> ExpiryAdapter + JSONObject::class.qualifiedName -> JSONObjectAdapter + SubscriptionId::class.qualifiedName -> SubscriptionIdAdapter + Topic::class.qualifiedName -> TopicAdapter + Ttl::class.qualifiedName -> TtlAdapter + else -> null + } + } + .addLast(KotlinJsonAdapterFactory()) + .add(polymorphicJsonAdapterFactory) + .build() \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/ClientListeners.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/ClientListeners.kt new file mode 100644 index 0000000000..993e181cdf --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/ClientListeners.kt @@ -0,0 +1,47 @@ +package com.walletconnect.walletconnectv2.client + +interface WalletConnectClientListener { + fun onSessionProposal(sessionProposal: WalletConnectClientData.SessionProposal) + fun onSessionRequest(sessionRequest: WalletConnectClientData.SessionRequest) + fun onSessionDelete(deletedSession: WalletConnectClientData.DeletedSession) + fun onSessionNotification(sessionNotification: WalletConnectClientData.SessionNotification) +} + +sealed interface WalletConnectClientListeners { + + fun onError(error: Throwable) + + interface Pairing : WalletConnectClientListeners { + fun onSuccess(settledPairing: WalletConnectClientData.SettledPairing) + } + + interface SessionReject : WalletConnectClientListeners { + fun onSuccess(rejectedSession: WalletConnectClientData.RejectedSession) + } + + interface SessionDelete : WalletConnectClientListeners { + fun onSuccess(deletedSession: WalletConnectClientData.DeletedSession) + } + + interface SessionApprove : WalletConnectClientListeners { + fun onSuccess(settledSession: WalletConnectClientData.SettledSession) + } + + interface SessionPayload : WalletConnectClientListeners + + interface SessionUpdate : WalletConnectClientListeners { + fun onSuccess(updatedSession: WalletConnectClientData.UpdatedSession) + } + + interface SessionUpgrade : WalletConnectClientListeners { + fun onSuccess(upgradedSession: WalletConnectClientData.UpgradedSession) + } + + interface SessionPing : WalletConnectClientListeners { + fun onSuccess(topic: String) + } + + interface Notification : WalletConnectClientListeners { + fun onSuccess(topic: String) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/ClientTypes.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/ClientTypes.kt new file mode 100644 index 0000000000..51c62bc165 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/ClientTypes.kt @@ -0,0 +1,38 @@ +package com.walletconnect.walletconnectv2.client + +import android.app.Application +import com.walletconnect.walletconnectv2.common.AppMetaData + +sealed class ClientTypes { + + data class InitialParams( + val application: Application, + val useTls: Boolean = true, + val hostName: String = WALLET_CONNECT_URL, + val projectId: String = "", + val isController: Boolean = true, + val metadata: AppMetaData = AppMetaData() + ) : ClientTypes() + + data class PairParams(val uri: String) : ClientTypes() + + data class ApproveParams(val proposal: WalletConnectClientData.SessionProposal, val accounts: List) : ClientTypes() + + data class RejectParams(val rejectionReason: String, val proposalTopic: String) : ClientTypes() + + data class DisconnectParams(val sessionTopic: String, val reason: String) : ClientTypes() + + data class ResponseParams(val sessionTopic: String, val jsonRpcResponse: WalletConnectClientData.JsonRpcResponse) : ClientTypes() + + data class UpdateParams(val sessionTopic: String, val sessionState: WalletConnectClientData.SessionState) : ClientTypes() + + data class UpgradeParams(val topic: String, val permissions: WalletConnectClientData.SessionPermissions) : ClientTypes() + + data class PingParams(val topic: String) : ClientTypes() + + data class NotificationParams(val topic: String, val notification: WalletConnectClientData.Notification) : ClientTypes() + + companion object { + private const val WALLET_CONNECT_URL = "relay.walletconnect.com" + } +} diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/WalletConnectClientData.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/WalletConnectClientData.kt new file mode 100644 index 0000000000..37e5557ed0 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/client/WalletConnectClientData.kt @@ -0,0 +1,105 @@ +package com.walletconnect.walletconnectv2.client + +import com.walletconnect.walletconnectv2.common.AppMetaData +import java.net.URI + +sealed class WalletConnectClientData { + + data class SessionProposal( + val name: String, + val description: String, + val url: String, + val icons: List, + val chains: List, + val methods: List, + val types: List, + val topic: String, + val proposerPublicKey: String, + val ttl: Long, + val accounts: List + ) : WalletConnectClientData() { + val icon: String = icons.first().toString() + } + + data class SessionRequest( + val topic: String, + val chainId: String?, + val request: JSONRPCRequest + ) : WalletConnectClientData() { + + data class JSONRPCRequest( + val id: Long, + val method: String, + val params: String + ) : WalletConnectClientData() + } + + data class SettledSession( + val topic: String, + val accounts: List, + val peerAppMetaData: AppMetaData?, + val permissions: Permissions + ) : WalletConnectClientData() { + + data class Permissions( + val blockchain: Blockchain, + val jsonRpc: JsonRpc, + val notifications: Notifications + ) { + data class Blockchain(val chains: List) + + data class JsonRpc(val methods: List) + + data class Notifications(val types: List) + } + } + + data class SessionState(val accounts: List) : WalletConnectClientData() + + data class SettledPairing(val topic: String) : WalletConnectClientData() + + data class RejectedSession(val topic: String, val reason: String) : WalletConnectClientData() + + data class DeletedSession(val topic: String, val reason: String) : WalletConnectClientData() + + data class UpgradedSession(val topic: String, val permissions: SessionPermissions) : WalletConnectClientData() + + data class SessionPermissions(val blockchain: Blockchain? = null, val jsonRpc: Jsonrpc? = null) : WalletConnectClientData() + + data class Blockchain(val chains: List) : WalletConnectClientData() + + data class Jsonrpc(val methods: List) : WalletConnectClientData() + + data class UpdatedSession(val topic: String, val accounts: List) : WalletConnectClientData() + + data class SessionNotification( + val topic: String, + val type: String, + val data: String + ) : WalletConnectClientData() + + data class Notification( + val type: String, + val data: String + ) : WalletConnectClientData() + + sealed class JsonRpcResponse : WalletConnectClientData() { + abstract val id: Long + val jsonrpc: String = "2.0" + + data class JsonRpcResult( + override val id: Long, + val result: String + ) : JsonRpcResponse() + + data class JsonRpcError( + override val id: Long, + val error: Error + ) : JsonRpcResponse() + + data class Error( + val code: Long, + val message: String + ) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/ClientSyncJsonRpc.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/ClientSyncJsonRpc.kt new file mode 100644 index 0000000000..f8378932dd --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/ClientSyncJsonRpc.kt @@ -0,0 +1,5 @@ +package com.walletconnect.walletconnectv2.clientsync + +interface ClientSyncJsonRpc { + val id: Long +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/Pairing.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/Pairing.kt new file mode 100644 index 0000000000..15021c008a --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/Pairing.kt @@ -0,0 +1,77 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.json.JSONObject +import com.walletconnect.walletconnectv2.ClientParams +import com.walletconnect.walletconnectv2.clientsync.pairing.after.payload.ProposalRequest +import com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal.PairingProposedPermissions +import com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal.PairingProposer +import com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal.PairingSignal +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingParticipant +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingState +import com.walletconnect.walletconnectv2.clientsync.session.after.params.Reason +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.common.network.adapters.ExpiryAdapter +import com.walletconnect.walletconnectv2.common.network.adapters.JSONObjectAdapter +import com.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter + +sealed class Pairing : ClientParams { + + data class Proposal( + val topic: Topic, + val relay: JSONObject, + val pairingProposer: PairingProposer, + val pairingSignal: PairingSignal?, + val permissions: PairingProposedPermissions?, + val ttl: Ttl + ) : Pairing() + + @JsonClass(generateAdapter = true) + data class Success( + @Json(name = "topic") + @TopicAdapter.Qualifier + val settledTopic: Topic, + @Json(name = "relay") + @JSONObjectAdapter.Qualifier + val relay: JSONObject, + @Json(name = "responder") + val responder: PairingParticipant, + @Json(name = "expiry") + @ExpiryAdapter.Qualifier + val expiry: Expiry, + @Json(name = "state") + val state: PairingState + ) : Pairing() + + class Failure(val reason: String) : Pairing() + + @JsonClass(generateAdapter = true) + data class PayloadParams( + @Json(name = "request") + val request: ProposalRequest + ) : Pairing() + + @JsonClass(generateAdapter = true) + class DeleteParams( + @Json(name = "reason") + val reason: Reason + ) : Pairing() + + class PingParams : Pairing() + + data class NotificationParams( + @Json(name = "type") + val type: String, + @Json(name = "data") + val data: Any + ) : Pairing() + + @JsonClass(generateAdapter = true) + data class UpdateParams( + @Json(name = "state") + val state: PairingState + ) : Pairing() +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/SettledPairingSequence.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/SettledPairingSequence.kt new file mode 100644 index 0000000000..97f3465e97 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/SettledPairingSequence.kt @@ -0,0 +1,16 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing + +import org.json.JSONObject +import com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal.PairingPermissions +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.crypto.data.PublicKey + +data class SettledPairingSequence( + val settledTopic: Topic, + val relay: JSONObject, + val selfPublicKey: PublicKey, + val peerPublicKey: PublicKey, + val sequencePermissions: PairingPermissions, + val expiry: Expiry +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/after/PostSettlementPairing.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/after/PostSettlementPairing.kt new file mode 100644 index 0000000000..0c1853f698 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/after/PostSettlementPairing.kt @@ -0,0 +1,74 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing.after + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.walletconnect.walletconnectv2.clientsync.ClientSyncJsonRpc +import com.walletconnect.walletconnectv2.clientsync.pairing.Pairing +import com.walletconnect.walletconnectv2.jsonrpc.utils.JsonRpcMethod + +sealed class PostSettlementPairing : ClientSyncJsonRpc { + abstract override val id: Long + abstract val method: String + abstract val jsonrpc: String + abstract val params: Pairing + + @JsonClass(generateAdapter = true) + data class PairingPayload( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_PAIRING_PAYLOAD, + @Json(name = "params") + override val params: Pairing.PayloadParams + ) : PostSettlementPairing() + + @JsonClass(generateAdapter = true) + data class SessionDelete( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_PAIRING_DELETE, + @Json(name = "params") + override val params: Pairing.DeleteParams + ) : PostSettlementPairing() + + @JsonClass(generateAdapter = true) + data class PairingUpdate( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_PAIRING_UPDATE, + @Json(name = "params") + override val params: Pairing.UpdateParams + ) : PostSettlementPairing() + + @JsonClass(generateAdapter = true) + data class PairingPing( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_PAIRING_PING, + @Json(name = "params") + override val params: Pairing.PingParams + ) : PostSettlementPairing() + + @JsonClass(generateAdapter = true) + data class PairingNotification( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_PAIRING_NOTIFICATION, + @Json(name = "params") + override val params: Pairing.NotificationParams + ) : PostSettlementPairing() +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/after/payload/ProposalRequest.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/after/payload/ProposalRequest.kt new file mode 100644 index 0000000000..c6cb1f6972 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/after/payload/ProposalRequest.kt @@ -0,0 +1,13 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing.after.payload + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.walletconnect.walletconnectv2.clientsync.session.Session + +@JsonClass(generateAdapter = true) +data class ProposalRequest( + @Json(name = "method") + val method: String, + @Json(name = "params") + val params: Session.Proposal +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PreSettlementPairing.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/PreSettlementPairing.kt similarity index 57% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PreSettlementPairing.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/PreSettlementPairing.kt index d494520d3d..64eb17f91e 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PreSettlementPairing.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/PreSettlementPairing.kt @@ -1,13 +1,15 @@ -package org.walletconnect.walletconnectv2.clientsync +package com.walletconnect.walletconnectv2.clientsync.pairing.before import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.clientsync.pairing.Pairing +import com.walletconnect.walletconnectv2.clientsync.ClientSyncJsonRpc +import com.walletconnect.walletconnectv2.clientsync.pairing.Pairing +import com.walletconnect.walletconnectv2.jsonrpc.utils.JsonRpcMethod -sealed class PreSettlementPairing { - abstract val id: Long - abstract val jsonrpc: String +sealed class PreSettlementPairing : ClientSyncJsonRpc { + abstract override val id: Long abstract val method: String + abstract val jsonrpc: String abstract val params: Pairing @JsonClass(generateAdapter = true) @@ -17,7 +19,7 @@ sealed class PreSettlementPairing { @Json(name = "jsonrpc") override val jsonrpc: String = "2.0", @Json(name = "method") - override val method: String = "wc_pairingApprove", + override val method: String = JsonRpcMethod.WC_PAIRING_APPROVE, @Json(name = "params") override val params: Pairing.Success ) : PreSettlementPairing() @@ -25,7 +27,8 @@ sealed class PreSettlementPairing { data class Reject( override val id: Long, override val jsonrpc: String = "2.0", - override val method: String = "wc_pairingReject", + override val method: String = JsonRpcMethod.WC_PAIRING_REJECT, override val params: Pairing.Failure ) : PreSettlementPairing() } + diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/JsonRPC.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/JsonRPC.kt new file mode 100644 index 0000000000..6898f5abd5 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/JsonRPC.kt @@ -0,0 +1,4 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal + + +data class JsonRPC(val methods: List) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingPermissions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingPermissions.kt new file mode 100644 index 0000000000..cb9b204f9c --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingPermissions.kt @@ -0,0 +1,9 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal + +import com.squareup.moshi.JsonClass +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingParticipant + +@JsonClass(generateAdapter = true) +data class PairingPermissions( + val controller: PairingParticipant +) diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingProposedPermissions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingProposedPermissions.kt new file mode 100644 index 0000000000..9228e06071 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingProposedPermissions.kt @@ -0,0 +1,4 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal + + +data class PairingProposedPermissions(val jsonRPC: JsonRPC) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingProposer.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingProposer.kt new file mode 100644 index 0000000000..633104e20f --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingProposer.kt @@ -0,0 +1,3 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal + +data class PairingProposer(val publicKey: String, val controller: Boolean) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingSignal.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingSignal.kt new file mode 100644 index 0000000000..f6aba74125 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingSignal.kt @@ -0,0 +1,3 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal + +data class PairingSignal(val type: String, val params: PairingSignalParams) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingSignalParams.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingSignalParams.kt new file mode 100644 index 0000000000..7548e3eff8 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/proposal/PairingSignalParams.kt @@ -0,0 +1,4 @@ +package com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal + + +data class PairingSignalParams(val uri: String) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/success/PairingParticipant.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/success/PairingParticipant.kt similarity index 62% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/success/PairingParticipant.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/success/PairingParticipant.kt index 02539ebf1c..538db733d0 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/success/PairingParticipant.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/success/PairingParticipant.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing.success +package com.walletconnect.walletconnectv2.clientsync.pairing.before.success import com.squareup.moshi.JsonClass diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/success/PairingState.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/success/PairingState.kt similarity index 59% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/success/PairingState.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/success/PairingState.kt index bb9d090594..a7588442d2 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/success/PairingState.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/pairing/before/success/PairingState.kt @@ -1,8 +1,8 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing.success +package com.walletconnect.walletconnectv2.clientsync.pairing.before.success import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.common.AppMetaData +import com.walletconnect.walletconnectv2.common.AppMetaData @JsonClass(generateAdapter = true) data class PairingState( diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/Session.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/Session.kt new file mode 100644 index 0000000000..12801207d7 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/Session.kt @@ -0,0 +1,90 @@ +package com.walletconnect.walletconnectv2.clientsync.session + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.walletconnect.walletconnectv2.ClientParams +import com.walletconnect.walletconnectv2.clientsync.session.after.params.Reason +import com.walletconnect.walletconnectv2.clientsync.session.after.params.SessionPermissions +import com.walletconnect.walletconnectv2.clientsync.session.after.params.SessionRequest +import com.walletconnect.walletconnectv2.clientsync.session.before.proposal.RelayProtocolOptions +import com.walletconnect.walletconnectv2.clientsync.session.before.proposal.SessionProposedPermissions +import com.walletconnect.walletconnectv2.clientsync.session.before.proposal.SessionProposer +import com.walletconnect.walletconnectv2.clientsync.session.before.proposal.SessionSignal +import com.walletconnect.walletconnectv2.clientsync.session.before.success.SessionParticipant +import com.walletconnect.walletconnectv2.clientsync.session.common.SessionState +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.common.network.adapters.ExpiryAdapter +import com.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter +import com.walletconnect.walletconnectv2.common.network.adapters.TtlAdapter + +sealed class Session: ClientParams { + + @JsonClass(generateAdapter = true) + data class Proposal( + @Json(name = "topic") + @field:TopicAdapter.Qualifier + val topic: Topic, + @Json(name = "relay") + val relay: RelayProtocolOptions, + @Json(name = "proposer") + val proposer: SessionProposer, + @Json(name = "signal") + val signal: SessionSignal, + @Json(name = "permissions") + val permissions: SessionProposedPermissions, + @Json(name = "ttl") + @field:TtlAdapter.Qualifier + val ttl: Ttl + ) : Session() + + @JsonClass(generateAdapter = true) + data class Success( + @Json(name = "relay") + val relay: RelayProtocolOptions, + @Json(name = "responder") + val responder: SessionParticipant, + @Json(name = "expiry") + @ExpiryAdapter.Qualifier + val expiry: Expiry, + @Json(name = "state") + val state: SessionState + ) : Session() + + @JsonClass(generateAdapter = true) + class Failure(val reason: String) : Session() + + @JsonClass(generateAdapter = true) + data class SessionPayloadParams( + @Json(name = "request") + val request: SessionRequest, + @Json(name = "chainId") + val chainId: String? + ) : Session() + + @JsonClass(generateAdapter = true) + class DeleteParams( + @Json(name = "reason") + val reason: Reason + ) : Session() + + class UpdateParams( + @Json(name = "state") + val state: SessionState + ) : Session() + + data class SessionPermissionsParams( + @Json(name = "permissions") + val permissions: SessionPermissions + ) : Session() + + class PingParams : Session() + + data class NotificationParams( + @Json(name = "type") + val type: String, + @Json(name = "data") + val data: Any + ) : Session() +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/SettledSessionSequence.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/SettledSessionSequence.kt new file mode 100644 index 0000000000..6f5aed20c6 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/SettledSessionSequence.kt @@ -0,0 +1,27 @@ +package com.walletconnect.walletconnectv2.clientsync.session + +import com.walletconnect.walletconnectv2.clientsync.session.before.proposal.RelayProtocolOptions +import com.walletconnect.walletconnectv2.clientsync.session.common.SessionState +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey + +data class SettledSessionSequence( + val topic: Topic, + val relay: RelayProtocolOptions, + val selfPublicKey: PublicKey, + val peerPublicKey: PublicKey, + val permissions: SettledSessionPermissions, + val sharedKey: SharedKey, + val expiry: Expiry, + val state: SessionState +) + +data class SettledSessionPermissions( + val controller: Controller +) + +data class Controller( + val publicKey: String +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/PostSettlementSession.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/PostSettlementSession.kt new file mode 100644 index 0000000000..66aebf72d3 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/PostSettlementSession.kt @@ -0,0 +1,86 @@ +package com.walletconnect.walletconnectv2.clientsync.session.after + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.walletconnect.walletconnectv2.clientsync.ClientSyncJsonRpc +import com.walletconnect.walletconnectv2.clientsync.session.Session +import com.walletconnect.walletconnectv2.jsonrpc.utils.JsonRpcMethod + +sealed class PostSettlementSession : ClientSyncJsonRpc { + abstract override val id: Long + abstract val method: String + abstract val jsonrpc: String + abstract val params: Session + + @JsonClass(generateAdapter = true) + data class SessionPayload( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_SESSION_PAYLOAD, + @Json(name = "params") + override val params: Session.SessionPayloadParams + ) : PostSettlementSession() + + @JsonClass(generateAdapter = true) + data class SessionDelete( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_SESSION_DELETE, + @Json(name = "params") + override val params: Session.DeleteParams + ) : PostSettlementSession() + + @JsonClass(generateAdapter = true) + data class SessionUpdate( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_SESSION_UPDATE, + @Json(name = "params") + override val params: Session.UpdateParams + ) : PostSettlementSession() + + @JsonClass(generateAdapter = true) + data class SessionUpgrade( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_SESSION_UPGRADE, + @Json(name = "params") + override val params: Session.SessionPermissionsParams + ) : PostSettlementSession() + + @JsonClass(generateAdapter = true) + data class SessionPing( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_SESSION_PING, + @Json(name = "params") + override val params: Session.PingParams + ) : PostSettlementSession() + + @JsonClass(generateAdapter = true) + data class SessionNotification( + @Json(name = "id") + override val id: Long, + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "method") + override val method: String = JsonRpcMethod.WC_SESSION_NOTIFICATION, + @Json(name = "params") + override val params: Session.NotificationParams + ) : PostSettlementSession() +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/Reason.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/Reason.kt new file mode 100644 index 0000000000..a32773befb --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/Reason.kt @@ -0,0 +1,13 @@ +package com.walletconnect.walletconnectv2.clientsync.session.after.params + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import com.walletconnect.walletconnectv2.util.DefaultId + +@JsonClass(generateAdapter = true) +data class Reason( + @Json(name = "code") + val code: Int = Int.DefaultId, + @Json(name = "message") + val message: String +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/SessionPermissions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/SessionPermissions.kt new file mode 100644 index 0000000000..fdf682ee76 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/SessionPermissions.kt @@ -0,0 +1,11 @@ +package com.walletconnect.walletconnectv2.clientsync.session.after.params + +import com.squareup.moshi.Json +import com.walletconnect.walletconnectv2.clientsync.session.before.proposal.SessionProposedPermissions + +data class SessionPermissions( + @Json(name = "blockchain") + val blockchain: SessionProposedPermissions.Blockchain? = null, + @Json(name = "jsonrpc") + val jsonRpc: SessionProposedPermissions.JsonRpc? = null, +) diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/SessionRequest.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/SessionRequest.kt new file mode 100644 index 0000000000..58f2998000 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/after/params/SessionRequest.kt @@ -0,0 +1,12 @@ +package com.walletconnect.walletconnectv2.clientsync.session.after.params + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SessionRequest( + @Json(name = "method") + val method: String, + @Json(name = "params") + val params: Any +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PreSettlementSession.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/PreSettlementSession.kt similarity index 64% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PreSettlementSession.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/PreSettlementSession.kt index 6509ccb205..ca8fffe960 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PreSettlementSession.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/PreSettlementSession.kt @@ -1,13 +1,15 @@ -package org.walletconnect.walletconnectv2.clientsync +package com.walletconnect.walletconnectv2.clientsync.session.before import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.clientsync.session.Session +import com.walletconnect.walletconnectv2.clientsync.ClientSyncJsonRpc +import com.walletconnect.walletconnectv2.clientsync.session.Session +import com.walletconnect.walletconnectv2.jsonrpc.utils.JsonRpcMethod -sealed class PreSettlementSession { - abstract val id: Long - abstract val jsonrpc: String +sealed class PreSettlementSession : ClientSyncJsonRpc { + abstract override val id: Long abstract val method: String + abstract val jsonrpc: String abstract val params: Session @JsonClass(generateAdapter = true) @@ -17,7 +19,7 @@ sealed class PreSettlementSession { @Json(name = "jsonrpc") override val jsonrpc: String = "2.0", @Json(name = "method") - override val method: String = "wc_sessionPropose", + override val method: String = JsonRpcMethod.WC_SESSION_PROPOSE, @Json(name = "params") override val params: Session.Proposal ) : PreSettlementSession() @@ -29,7 +31,7 @@ sealed class PreSettlementSession { @Json(name = "jsonrpc") override val jsonrpc: String = "2.0", @Json(name = "method") - override val method: String = "wc_sessionApprove", + override val method: String = JsonRpcMethod.WC_SESSION_APPROVE, @Json(name = "params") override val params: Session.Success ) : PreSettlementSession() @@ -37,7 +39,7 @@ sealed class PreSettlementSession { data class Reject( override val id: Long, override val jsonrpc: String = "2.0", - override val method: String = "wc_sessionReject", + override val method: String = JsonRpcMethod.WC_SESSION_REJECT, override val params: Session.Failure ) : PreSettlementSession() diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/RelayProtocolOptions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/RelayProtocolOptions.kt new file mode 100644 index 0000000000..ad9b7d22a3 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/RelayProtocolOptions.kt @@ -0,0 +1,5 @@ +package com.walletconnect.walletconnectv2.clientsync.session.before.proposal + +data class RelayProtocolOptions( + val protocol: String = "waku" +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionProposedPermissions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionProposedPermissions.kt similarity index 90% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionProposedPermissions.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionProposedPermissions.kt index 14f185d4b8..9a472fe99c 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionProposedPermissions.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionProposedPermissions.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.clientsync.session.proposal +package com.walletconnect.walletconnectv2.clientsync.session.before.proposal import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionProposer.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionProposer.kt similarity index 69% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionProposer.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionProposer.kt index e88858075c..712f75f85d 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionProposer.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionProposer.kt @@ -1,8 +1,8 @@ -package org.walletconnect.walletconnectv2.clientsync.session.proposal +package com.walletconnect.walletconnectv2.clientsync.session.before.proposal import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.common.AppMetaData +import com.walletconnect.walletconnectv2.common.AppMetaData @JsonClass(generateAdapter = true) data class SessionProposer( diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionSignal.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionSignal.kt similarity index 64% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionSignal.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionSignal.kt index 628a25daab..2af58d5dd9 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/SessionSignal.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/proposal/SessionSignal.kt @@ -1,9 +1,9 @@ -package org.walletconnect.walletconnectv2.clientsync.session.proposal +package com.walletconnect.walletconnectv2.clientsync.session.before.proposal import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter @JsonClass(generateAdapter = true) data class SessionSignal( @@ -17,4 +17,4 @@ data class SessionSignal( @field:TopicAdapter.Qualifier val topic: Topic ) -} +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/success/SessionParticipant.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/success/SessionParticipant.kt similarity index 65% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/success/SessionParticipant.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/success/SessionParticipant.kt index df781a1b03..5dc9fe3c87 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/success/SessionParticipant.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/before/success/SessionParticipant.kt @@ -1,8 +1,8 @@ -package org.walletconnect.walletconnectv2.clientsync.session.success +package com.walletconnect.walletconnectv2.clientsync.session.before.success import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.common.AppMetaData +import com.walletconnect.walletconnectv2.common.AppMetaData @JsonClass(generateAdapter = true) data class SessionParticipant( diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/success/SessionState.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/common/SessionState.kt similarity index 64% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/success/SessionState.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/common/SessionState.kt index 35179b07e6..13c70f1b07 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/success/SessionState.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/clientsync/session/common/SessionState.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.clientsync.session.success +package com.walletconnect.walletconnectv2.clientsync.session.common import com.squareup.moshi.JsonClass diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/AppMetaData.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/AppMetaData.kt similarity index 88% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/AppMetaData.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/AppMetaData.kt index 2b4f2e11b5..d7d9059a18 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/AppMetaData.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/AppMetaData.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.common +package com.walletconnect.walletconnectv2.common import com.squareup.moshi.Json import com.squareup.moshi.JsonClass diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/ControllerType.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/ControllerType.kt new file mode 100644 index 0000000000..e8b5ee2f99 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/ControllerType.kt @@ -0,0 +1,5 @@ +package com.walletconnect.walletconnectv2.common + +enum class ControllerType { + CONTROLLER, NON_CONTROLLER +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/DomainObjects.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/DomainObjects.kt similarity index 74% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/DomainObjects.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/DomainObjects.kt index 7eb533e048..468d8d26b1 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/DomainObjects.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/DomainObjects.kt @@ -1,7 +1,7 @@ -package org.walletconnect.walletconnectv2.common +package com.walletconnect.walletconnectv2.common // TODO: Look into finding a way to convert these into value classes and still have Moshi deserialize them without adding property inside JSON object -data class Topic(val topicValue: String) +data class Topic(val value: String) data class Ttl(val seconds: Long) diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/MappingFunctions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/MappingFunctions.kt new file mode 100644 index 0000000000..aa9c51134f --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/MappingFunctions.kt @@ -0,0 +1,140 @@ +@file:JvmName("MappingFunctions") + +package com.walletconnect.walletconnectv2.common + +import org.json.JSONObject +import com.walletconnect.walletconnectv2.client.WalletConnectClientData +import com.walletconnect.walletconnectv2.clientsync.pairing.Pairing +import com.walletconnect.walletconnectv2.clientsync.pairing.before.PreSettlementPairing +import com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal.* +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingParticipant +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingState +import com.walletconnect.walletconnectv2.clientsync.session.Session +import com.walletconnect.walletconnectv2.clientsync.session.after.params.SessionPermissions +import com.walletconnect.walletconnectv2.clientsync.session.before.proposal.SessionProposedPermissions +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.engine.model.EngineData +import com.walletconnect.walletconnectv2.jsonrpc.model.JsonRpcResponse +import com.walletconnect.walletconnectv2.jsonrpc.utils.JsonRpcMethod +import com.walletconnect.walletconnectv2.relay.waku.WakuNetworkRepository +import com.walletconnect.walletconnectv2.relay.walletconnect.WalletConnectRelayer +import java.net.URI +import kotlin.time.Duration + +internal fun String.toPairProposal(): Pairing.Proposal { + val properUriString = if (contains("wc://")) this else replace("wc:", "wc://") + val pairUri = URI(properUriString) + val mapOfQueryParameters: Map = + pairUri.query.split("&") + .associate { query -> query.substringBefore("=") to query.substringAfter("=") } + val relay = JSONObject(mapOfQueryParameters["relay"] ?: "{}") + val publicKey = mapOfQueryParameters["publicKey"] ?: "" + val controller: Boolean = mapOfQueryParameters["controller"].toBoolean() + val ttl: Long = Duration.days(30).inWholeSeconds + + return Pairing.Proposal( + topic = Topic(pairUri.userInfo), + relay = relay, + pairingProposer = PairingProposer(publicKey, controller), + pairingSignal = PairingSignal("uri", PairingSignalParams(properUriString)), + permissions = PairingProposedPermissions(JsonRPC(listOf(JsonRpcMethod.WC_SESSION_PROPOSE))), + ttl = Ttl(ttl) + ) +} + +internal fun Pairing.Proposal.toPairingSuccess(settleTopic: Topic, expiry: Expiry, selfPublicKey: PublicKey): Pairing.Success = + Pairing.Success( + settledTopic = settleTopic, + relay = relay, + responder = PairingParticipant(publicKey = selfPublicKey.keyAsHex), + expiry = expiry, + state = PairingState(null) + ) + +internal fun Pairing.Proposal.toApprove( + id: Long, + settleTopic: Topic, + expiry: Expiry, + selfPublicKey: PublicKey +): PreSettlementPairing.Approve = com.walletconnect.walletconnectv2.clientsync.pairing.before.PreSettlementPairing.Approve(id = id, params = this.toPairingSuccess(settleTopic, expiry, selfPublicKey)) + +internal fun Session.Proposal.toSessionProposal(): EngineData.SessionProposal = + com.walletconnect.walletconnectv2.engine.model.EngineData.SessionProposal( + name = this.proposer.metadata?.name!!, + description = this.proposer.metadata.description, + url = this.proposer.metadata.url, + icons = this.proposer.metadata.icons.map { URI(it) }, + chains = this.permissions.blockchain.chains, + methods = this.permissions.jsonRpc.methods, + types = this.permissions.notifications.types, + topic = this.topic.value, + proposerPublicKey = this.proposer.publicKey, + ttl = this.ttl.seconds, + accounts = listOf() + ) + +internal fun WalletConnectRelayer.RelayFactory.toWakuNetworkInitParams(): WakuNetworkRepository.WakuNetworkFactory = + WakuNetworkRepository.WakuNetworkFactory(useTls, hostName, projectId, application) + +internal fun EngineData.SessionProposal.toClientSessionProposal(): WalletConnectClientData.SessionProposal = + WalletConnectClientData.SessionProposal(name, description, url, icons, chains, methods, types, topic, proposerPublicKey, ttl, accounts) + +internal fun WalletConnectClientData.SessionProposal.toEngineSessionProposal(accountList: List): EngineData.SessionProposal = + com.walletconnect.walletconnectv2.engine.model.EngineData.SessionProposal(name, description, url, icons, chains, methods, types, topic, proposerPublicKey, ttl, accountList) + +internal fun EngineData.SettledSession.toClientSettledSession(): WalletConnectClientData.SettledSession = + WalletConnectClientData.SettledSession(topic, accounts, peerAppMetaData, permissions.toClientSettledSessionPermissions()) + +private fun EngineData.SettledSession.Permissions.toClientSettledSessionPermissions(): WalletConnectClientData.SettledSession.Permissions = + WalletConnectClientData.SettledSession.Permissions(blockchain.toClientSettledSessionBlockchain(), jsonRpc.toClientSettledSessionJsonRpc(), notifications.toClientSettledSessionNotifications()) + +private fun EngineData.SettledSession.Permissions.Blockchain.toClientSettledSessionBlockchain(): WalletConnectClientData.SettledSession.Permissions.Blockchain = + WalletConnectClientData.SettledSession.Permissions.Blockchain(chains) + +private fun EngineData.SettledSession.Permissions.JsonRpc.toClientSettledSessionJsonRpc(): WalletConnectClientData.SettledSession.Permissions.JsonRpc = + WalletConnectClientData.SettledSession.Permissions.JsonRpc(methods) + +private fun EngineData.SettledSession.Permissions.Notifications.toClientSettledSessionNotifications(): WalletConnectClientData.SettledSession.Permissions.Notifications = + WalletConnectClientData.SettledSession.Permissions.Notifications(types) + +internal fun EngineData.SessionRequest.toClientSessionRequest(): WalletConnectClientData.SessionRequest = + WalletConnectClientData.SessionRequest( + topic, + chainId, + WalletConnectClientData.SessionRequest.JSONRPCRequest(request.id, request.method, request.params) + ) + +internal fun WalletConnectClientData.JsonRpcResponse.JsonRpcResult.toEngineRpcResult(): JsonRpcResponse.JsonRpcResult = + JsonRpcResponse.JsonRpcResult(id, result) + +internal fun WalletConnectClientData.JsonRpcResponse.JsonRpcError.toEngineRpcError(): JsonRpcResponse.JsonRpcError = + JsonRpcResponse.JsonRpcError(id, JsonRpcResponse.Error(error.code, error.message)) + +internal fun WalletConnectClientData.SessionState.toEngineSessionState(): EngineData.SessionState = com.walletconnect.walletconnectv2.engine.model.EngineData.SessionState(accounts) + +internal fun WalletConnectClientData.Notification.toEngineNotification(): EngineData.Notification = + com.walletconnect.walletconnectv2.engine.model.EngineData.Notification(type, data) + +internal fun EngineData.DeletedSession.toClientDeletedSession(): WalletConnectClientData.DeletedSession = + WalletConnectClientData.DeletedSession(topic, reason) + +internal fun EngineData.SessionNotification.toClientSessionNotification(): WalletConnectClientData.SessionNotification = + WalletConnectClientData.SessionNotification(topic, type, data) + +internal fun WalletConnectClientData.SessionPermissions.toEngineSessionPermissions(): EngineData.SessionPermissions = + com.walletconnect.walletconnectv2.engine.model.EngineData.SessionPermissions( + blockchain?.chains?.let { chains -> com.walletconnect.walletconnectv2.engine.model.EngineData.Blockchain(chains) }, + jsonRpc?.methods?.let { methods -> com.walletconnect.walletconnectv2.engine.model.EngineData.Jsonrpc(methods) } + ) + +internal fun EngineData.SessionPermissions.toClientPerms(): WalletConnectClientData.SessionPermissions = + WalletConnectClientData.SessionPermissions( + blockchain?.chains?.let { chains -> WalletConnectClientData.Blockchain(chains) }, + jsonRpc?.methods?.let { methods -> WalletConnectClientData.Jsonrpc(methods) } + ) + +internal fun EngineData.SessionPermissions.toSessionsPermissions(): SessionPermissions = + SessionPermissions( + blockchain?.chains?.let { chains -> SessionProposedPermissions.Blockchain(chains) }, + jsonRpc?.methods?.let { methods -> SessionProposedPermissions.JsonRpc(methods) } + ) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapter.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapter.kt similarity index 78% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapter.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapter.kt index 258456f253..7aa5744824 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapter.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapter.kt @@ -1,8 +1,7 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.* -import okio.Buffer -import org.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Expiry object ExpiryAdapter: JsonAdapter() { diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/JSONObjectAdapter.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/JSONObjectAdapter.kt similarity index 83% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/JSONObjectAdapter.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/JSONObjectAdapter.kt index dd116bb6e2..145cb48a2e 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/JSONObjectAdapter.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/JSONObjectAdapter.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.* import okio.Buffer @@ -8,6 +8,7 @@ import org.json.JSONObject object JSONObjectAdapter: JsonAdapter() { @FromJson + @Qualifier override fun fromJson(reader: JsonReader): JSONObject? { // Here we're expecting the JSON object, it is processed as Map by Moshi return (reader.readJsonValue() as? Map)?.let { data -> @@ -21,7 +22,7 @@ object JSONObjectAdapter: JsonAdapter() { } @ToJson - override fun toJson(writer: JsonWriter, value: JSONObject?) { + override fun toJson(writer: JsonWriter, @Qualifier value: JSONObject?) { value?.let { writer.value(Buffer().writeUtf8(value.toString())) } } diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapter.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapter.kt similarity index 82% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapter.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapter.kt index 8fa8f4cf85..fe05f7a170 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapter.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapter.kt @@ -1,8 +1,7 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.* -import org.walletconnect.walletconnectv2.common.SubscriptionId -import org.walletconnect.walletconnectv2.util.jsonObject +import com.walletconnect.walletconnectv2.common.SubscriptionId object SubscriptionIdAdapter: JsonAdapter() { diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TopicAdapter.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TopicAdapter.kt similarity index 77% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TopicAdapter.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TopicAdapter.kt index d5681e7f6b..d599aaa0c9 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TopicAdapter.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TopicAdapter.kt @@ -1,8 +1,7 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.* -import org.walletconnect.walletconnectv2.common.SubscriptionId -import org.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.Topic object TopicAdapter: JsonAdapter() { @@ -26,7 +25,7 @@ object TopicAdapter: JsonAdapter() { @ToJson override fun toJson(writer: JsonWriter, @Qualifier value: Topic?) { if (value != null) { - writer.value(value.topicValue) + writer.value(value.value) } else { writer.value("") } diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TtlAdapter.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TtlAdapter.kt similarity index 86% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TtlAdapter.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TtlAdapter.kt index 5012e804b7..698d84409e 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TtlAdapter.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TtlAdapter.kt @@ -1,7 +1,7 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.* -import org.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.common.Ttl object TtlAdapter : JsonAdapter() { diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/Codec.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/Codec.kt new file mode 100644 index 0000000000..08db70cf9c --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/Codec.kt @@ -0,0 +1,10 @@ +package com.walletconnect.walletconnectv2.crypto + +import com.walletconnect.walletconnectv2.crypto.data.EncryptionPayload +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey + +interface Codec { + fun encrypt(message: String, sharedKey: SharedKey, publicKey: PublicKey): String + fun decrypt(payload: EncryptionPayload, sharedKey: SharedKey): String +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/CryptoManager.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/CryptoManager.kt new file mode 100644 index 0000000000..bcb0967d27 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/CryptoManager.kt @@ -0,0 +1,14 @@ +package com.walletconnect.walletconnectv2.crypto + +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.crypto.data.Key +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey + +interface CryptoManager { + fun generateKeyPair(): PublicKey + fun generateTopicAndSharedKey(self: PublicKey, peer: PublicKey): Pair + fun getKeyAgreement(topic: Topic): Pair + fun setEncryptionKeys(sharedKey: SharedKey, publicKey: PublicKey, topic: Topic) + fun removeKeys(tag: String) +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodec.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodec.kt similarity index 63% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodec.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodec.kt index 0603fa314b..84a76bed2d 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodec.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodec.kt @@ -1,10 +1,11 @@ -package org.walletconnect.walletconnectv2.crypto.codec - -import org.walletconnect.walletconnectv2.crypto.Codec -import org.walletconnect.walletconnectv2.crypto.data.EncryptionPayload -import org.walletconnect.walletconnectv2.crypto.data.PublicKey -import org.walletconnect.walletconnectv2.util.bytesToHex -import org.walletconnect.walletconnectv2.util.hexToBytes +package com.walletconnect.walletconnectv2.crypto.codec + +import com.walletconnect.walletconnectv2.crypto.Codec +import com.walletconnect.walletconnectv2.crypto.data.EncryptionPayload +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey +import com.walletconnect.walletconnectv2.util.bytesToHex +import com.walletconnect.walletconnectv2.util.hexToBytes import java.security.MessageDigest import java.security.SecureRandom import javax.crypto.Cipher @@ -14,43 +15,24 @@ import javax.crypto.spec.SecretKeySpec class AuthenticatedEncryptionCodec : Codec { - override fun encrypt( - message: String, - sharedKey: String, - publicKey: PublicKey - ): EncryptionPayload { - val (encryptionKey, authenticationKey) = getKeys(sharedKey) + override fun encrypt(message: String, sharedKey: SharedKey, publicKey: PublicKey): String { + val (encryptionKey, authenticationKey) = getKeys(sharedKey.keyAsHex) val data = message.toByteArray(Charsets.UTF_8) val iv: ByteArray = randomBytes(16) val cipher: Cipher = Cipher.getInstance(CIPHER_ALGORITHM) - cipher.init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(encryptionKey, AES_ALGORITHM), - IvParameterSpec(iv) - ) + cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(encryptionKey, AES_ALGORITHM), IvParameterSpec(iv)) val cipherText: ByteArray = cipher.doFinal(data) + val computedMac: String = computeHmac(cipherText, iv, authenticationKey, publicKey.keyAsHex.hexToBytes()) - val computedMac: String = - computeHmac(cipherText, iv, authenticationKey, publicKey.keyAsHex.hexToBytes()) - - return EncryptionPayload( - iv = iv.bytesToHex(), - publicKey = publicKey.keyAsHex, - mac = computedMac, - cipherText = cipherText.bytesToHex() - ) + return iv.bytesToHex() + publicKey.keyAsHex + computedMac + cipherText.bytesToHex() } - override fun decrypt( - payload: EncryptionPayload, - sharedKey: String - ): String { - val (encryptionKey, authenticationKey) = getKeys(sharedKey) + override fun decrypt(payload: EncryptionPayload, sharedKey: SharedKey): String { + val (encryptionKey, authenticationKey) = getKeys(sharedKey.keyAsHex) val data = payload.cipherText.hexToBytes() val iv = payload.iv.hexToBytes() - val computedHmac = computeHmac(data, iv, authenticationKey, payload.publicKey.hexToBytes()) if (computedHmac != payload.mac.lowercase()) { @@ -58,16 +40,11 @@ class AuthenticatedEncryptionCodec : Codec { } val cipher = Cipher.getInstance(CIPHER_ALGORITHM) - cipher.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(encryptionKey, AES_ALGORITHM), - IvParameterSpec(iv) - ) - + cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(encryptionKey, AES_ALGORITHM), IvParameterSpec(iv)) return String(cipher.doFinal(data), Charsets.UTF_8) } - fun getKeys(sharedKey: String): Pair { + internal fun getKeys(sharedKey: String): Pair { val hexKey = sharedKey.hexToBytes() val messageDigest: MessageDigest = MessageDigest.getInstance(HASH_ALGORITHM) val hashedKey: ByteArray = messageDigest.digest(hexKey) diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/data/EncryptionPayload.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/data/EncryptionPayload.kt similarity index 83% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/data/EncryptionPayload.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/data/EncryptionPayload.kt index eacf64093e..c65f2387dd 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/data/EncryptionPayload.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/data/EncryptionPayload.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.crypto.data +package com.walletconnect.walletconnectv2.crypto.data data class EncryptionPayload( val iv: String, diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/data/Keys.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/data/Keys.kt new file mode 100644 index 0000000000..ba73963ef5 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/data/Keys.kt @@ -0,0 +1,14 @@ +package com.walletconnect.walletconnectv2.crypto.data + +interface Key { + val keyAsHex: String +} + +@JvmInline +value class PublicKey(override val keyAsHex: String) : Key + +@JvmInline +value class PrivateKey(override val keyAsHex: String) : Key + +@JvmInline +value class SharedKey(override val keyAsHex: String) : Key \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/managers/BouncyCastleCryptoManager.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/managers/BouncyCastleCryptoManager.kt new file mode 100644 index 0000000000..ed2c8eeb6c --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/crypto/managers/BouncyCastleCryptoManager.kt @@ -0,0 +1,81 @@ +package com.walletconnect.walletconnectv2.crypto.managers + + +import org.bouncycastle.math.ec.rfc7748.X25519 +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.crypto.CryptoManager +import com.walletconnect.walletconnectv2.crypto.data.PrivateKey +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey +import com.walletconnect.walletconnectv2.storage.KeyChain +import com.walletconnect.walletconnectv2.storage.KeyStore +import com.walletconnect.walletconnectv2.util.bytesToHex +import com.walletconnect.walletconnectv2.util.hexToBytes +import java.security.MessageDigest +import java.security.SecureRandom +import com.walletconnect.walletconnectv2.crypto.data.Key as WCKey + +class BouncyCastleCryptoManager(private val keyChain: KeyStore = KeyChain()) : CryptoManager { + + override fun generateKeyPair(): PublicKey { + val publicKey = ByteArray(KEY_SIZE) + val privateKey = ByteArray(KEY_SIZE) + X25519.generatePrivateKey(SecureRandom(ByteArray(KEY_SIZE)), privateKey) + X25519.generatePublicKey(privateKey, 0, publicKey, 0) + setKeyPair(PublicKey(publicKey.bytesToHex().lowercase()), PrivateKey(privateKey.bytesToHex().lowercase())) + return PublicKey(publicKey.bytesToHex().lowercase()) + } + + internal fun getSharedKey(selfPrivate: PrivateKey, peerPublic: PublicKey): String { + val sharedKeyBytes = ByteArray(KEY_SIZE) + X25519.scalarMult(selfPrivate.keyAsHex.hexToBytes(), 0, peerPublic.keyAsHex.hexToBytes(), 0, sharedKeyBytes, 0) + return sharedKeyBytes.bytesToHex() + } + + override fun generateTopicAndSharedKey(self: PublicKey, peer: PublicKey): Pair { + val (publicKey, privateKey) = getKeyPair(self) + val sharedKeyBytes = ByteArray(KEY_SIZE) + X25519.scalarMult(privateKey.keyAsHex.hexToBytes(), 0, peer.keyAsHex.hexToBytes(), 0, sharedKeyBytes, 0) + val sharedKey = SharedKey(sharedKeyBytes.bytesToHex()) + val topic = generateTopic(sharedKey.keyAsHex) + setEncryptionKeys(sharedKey, publicKey, Topic(topic.value.lowercase())) + return Pair(sharedKey, topic) + } + + override fun setEncryptionKeys(sharedKey: SharedKey, publicKey: PublicKey, topic: Topic) { + keyChain.setKey(topic.value, sharedKey, publicKey) + } + + override fun removeKeys(tag: String) { + val (_, publicKey) = keyChain.getKeys(tag) + with(keyChain) { + deleteKeys(publicKey.lowercase()) + deleteKeys(tag) + } + } + + override fun getKeyAgreement(topic: Topic): Pair { + val (sharedKey, peerPublic) = keyChain.getKeys(topic.value) + return Pair(SharedKey(sharedKey), PublicKey(peerPublic)) + } + + internal fun setKeyPair(publicKey: PublicKey, privateKey: PrivateKey) { + keyChain.setKey(publicKey.keyAsHex, publicKey, privateKey) + } + + internal fun getKeyPair(wcKey: WCKey): Pair { + val (publicKeyHex, privateKeyHex) = keyChain.getKeys(wcKey.keyAsHex) + return Pair(PublicKey(publicKeyHex), PrivateKey(privateKeyHex)) + } + + private fun generateTopic(sharedKey: String): Topic { + val messageDigest: MessageDigest = MessageDigest.getInstance(SHA_256) + val hashedBytes: ByteArray = messageDigest.digest(sharedKey.hexToBytes()) + return Topic(hashedBytes.bytesToHex()) + } + + companion object { + private const val KEY_SIZE: Int = 32 + private const val SHA_256: String = "SHA-256" + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/EngineInteractor.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/EngineInteractor.kt new file mode 100644 index 0000000000..b0df82cb55 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/EngineInteractor.kt @@ -0,0 +1,465 @@ +package com.walletconnect.walletconnectv2.engine + +import android.app.Application +import com.walletconnect.walletconnectv2.clientsync.pairing.Pairing +import com.walletconnect.walletconnectv2.clientsync.pairing.SettledPairingSequence +import com.walletconnect.walletconnectv2.clientsync.pairing.after.PostSettlementPairing +import com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal.PairingPermissions +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingParticipant +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingState +import com.walletconnect.walletconnectv2.clientsync.session.Controller +import com.walletconnect.walletconnectv2.clientsync.session.Session +import com.walletconnect.walletconnectv2.clientsync.session.SettledSessionPermissions +import com.walletconnect.walletconnectv2.clientsync.session.SettledSessionSequence +import com.walletconnect.walletconnectv2.clientsync.session.after.PostSettlementSession +import com.walletconnect.walletconnectv2.clientsync.session.after.params.Reason +import com.walletconnect.walletconnectv2.clientsync.session.before.PreSettlementSession +import com.walletconnect.walletconnectv2.clientsync.session.before.proposal.RelayProtocolOptions +import com.walletconnect.walletconnectv2.clientsync.session.before.success.SessionParticipant +import com.walletconnect.walletconnectv2.clientsync.session.common.SessionState +import com.walletconnect.walletconnectv2.common.* +import com.walletconnect.walletconnectv2.crypto.CryptoManager +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey +import com.walletconnect.walletconnectv2.crypto.managers.BouncyCastleCryptoManager +import com.walletconnect.walletconnectv2.engine.model.EngineData +import com.walletconnect.walletconnectv2.engine.sequence.SequenceLifecycle +import com.walletconnect.walletconnectv2.jsonrpc.model.JsonRpcResponse +import com.walletconnect.walletconnectv2.relay.walletconnect.WalletConnectRelayer +import com.walletconnect.walletconnectv2.scope +import com.walletconnect.walletconnectv2.storage.SequenceStatus +import com.walletconnect.walletconnectv2.storage.StorageRepository +import com.walletconnect.walletconnectv2.util.Empty +import com.walletconnect.walletconnectv2.util.Logger +import com.walletconnect.walletconnectv2.util.generateId +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import org.json.JSONObject +import java.net.URI +import java.util.* + +internal class EngineInteractor { + //region provide with DI + // TODO: add logic to check hostName for ws/wss scheme with and without :// + private var relayer: WalletConnectRelayer = WalletConnectRelayer() + private lateinit var storageRepository: StorageRepository + private val crypto: CryptoManager = BouncyCastleCryptoManager() + //endregion + + private var metaData: AppMetaData? = null + private var controllerType = ControllerType.CONTROLLER + private val _sequenceEvent: MutableStateFlow = MutableStateFlow(SequenceLifecycle.Default) + val sequenceEvent: StateFlow = _sequenceEvent + + internal fun initialize(engine: EngineFactory) = with(engine) { + this@EngineInteractor.metaData = engine.metaData + this@EngineInteractor.controllerType = if (engine.isController) ControllerType.CONTROLLER else ControllerType.NON_CONTROLLER + WalletConnectRelayer.RelayFactory(useTLs, hostName, projectId, application).run { + relayer.initialize(this) + } + storageRepository = StorageRepository(null, engine.application) + collectClientSyncJsonRpc() + + relayer.isConnectionOpened + .filter { isConnected: Boolean -> isConnected } + .onEach { + coroutineScope { + launch(Dispatchers.IO) { resubscribeToSettledPairings() } + launch(Dispatchers.IO) { resubscribeToSettledSession() } + } + } + .launchIn(scope) + } + + internal fun pair(uri: String, onSuccess: (String) -> Unit, onFailure: (Throwable) -> Unit) { + val pairingProposal: Pairing.Proposal = uri.toPairProposal() + storageRepository.insertPairingProposal(pairingProposal.topic.value, uri, defaultSequenceExpirySeconds(), SequenceStatus.PENDING, controllerType) + relayer.subscribe(pairingProposal.topic) + val selfPublicKey: PublicKey = crypto.generateKeyPair() + val expiry = Expiry((Calendar.getInstance().timeInMillis / 1000) + pairingProposal.ttl.seconds) + val peerPublicKey = PublicKey(pairingProposal.pairingProposer.publicKey) + val controllerPublicKey = if (pairingProposal.pairingProposer.controller) { + peerPublicKey + } else { + selfPublicKey + } + val settledSequence = settlePairingSequence(pairingProposal.relay, selfPublicKey, peerPublicKey, controllerPublicKey, expiry) + val preSettlementPairingApprove = pairingProposal.toApprove(generateId(), settledSequence.settledTopic, expiry, selfPublicKey) + + relayer.isConnectionOpened + .filter { isOnline -> isOnline } + .onEach { + supervisorScope { + relayer.request(pairingProposal.topic, preSettlementPairingApprove) { result -> + result.fold( + onSuccess = { + relayer.unsubscribe(pairingProposal.topic) + relayer.subscribe(settledSequence.settledTopic) + storageRepository.updatePendingPairingToSettled( + pairingProposal.topic.value, + settledSequence.settledTopic.value, + expiry.seconds, + SequenceStatus.SETTLED + ) + onSuccess(settledSequence.settledTopic.value) + pairingUpdate(settledSequence) + }, + onFailure = { throwable -> onFailure(throwable) } + ) + } + cancel() + } + } + .launchIn(scope) + } + + private fun pairingUpdate(settledSequence: SettledPairingSequence) { + val pairingUpdate: PostSettlementPairing.PairingUpdate = + PostSettlementPairing.PairingUpdate(id = generateId(), params = Pairing.UpdateParams(state = PairingState(metaData))) + relayer.request(settledSequence.settledTopic, pairingUpdate) { result -> + result.fold( + onSuccess = { + /*TODO update Pairing's metadata in local storage + * Might not need to store pairing metadata because metadata is a global variable*/ + }, + onFailure = { error -> Logger.error("Pairing update error: $error") } + ) + } + } + + internal fun approve( + proposal: EngineData.SessionProposal, + onSuccess: (EngineData.SettledSession) -> Unit, + onFailure: (Throwable) -> Unit + ) { + val selfPublicKey: PublicKey = crypto.generateKeyPair() + val peerPublicKey = PublicKey(proposal.proposerPublicKey) + val sessionState = SessionState(proposal.accounts) + val expiry = Expiry((Calendar.getInstance().timeInMillis / 1000) + proposal.ttl) + val settledSession: SettledSessionSequence = + settleSessionSequence(RelayProtocolOptions(), selfPublicKey, peerPublicKey, expiry, sessionState) + relayer.subscribe(Topic(proposal.topic)) + val sessionApprove = PreSettlementSession.Approve( + id = generateId(), params = Session.Success( + relay = RelayProtocolOptions(), state = settledSession.state, expiry = expiry, + responder = SessionParticipant(selfPublicKey.keyAsHex, metadata = metaData) + ) + ) + + relayer.request(Topic(proposal.topic), sessionApprove) { result -> + result.fold( + onSuccess = { + relayer.unsubscribe(Topic(proposal.topic)) + relayer.subscribe(settledSession.topic) + + with(proposal) { + storageRepository.updateStatusToSessionApproval( + topic, + sessionApprove.id, + settledSession.topic.value, + sessionApprove.params.state.accounts, + sessionApprove.params.expiry.seconds + ) + + val engineDataSettledSession = EngineData.SettledSession( + settledSession.topic.value, + accounts, + AppMetaData(name, description, url, icons.map { iconUri -> iconUri.toString() }), + EngineData.SettledSession.Permissions( + EngineData.SettledSession.Permissions.Blockchain(chains), + EngineData.SettledSession.Permissions.JsonRpc(methods), + EngineData.SettledSession.Permissions.Notifications(types) + ) + ) + onSuccess(engineDataSettledSession) + } + }, + onFailure = { error -> onFailure(error) } + ) + } + } + + internal fun reject(reason: String, topic: String, onSuccess: (Pair) -> Unit, onFailure: (Throwable) -> Unit) { + val sessionReject = PreSettlementSession.Reject(id = generateId(), params = Session.Failure(reason = reason)) + onSuccess(Pair(topic, reason)) + storageRepository.deleteSession(topic) + relayer.request(Topic(topic), sessionReject) { result -> + result.fold( + onSuccess = {}, //TODO: Should we unsubscribe from topic? + onFailure = { error -> onFailure(error) } + ) + } + } + + internal fun disconnect(topic: String, reason: String, onSuccess: (Pair) -> Unit, onFailure: (Throwable) -> Unit) { + val sessionDelete = PostSettlementSession.SessionDelete(id = generateId(), params = Session.DeleteParams(Reason(message = reason))) + storageRepository.deleteSession(topic) + relayer.unsubscribe(Topic(topic)) + onSuccess(Pair(topic, reason)) + relayer.request(Topic(topic), sessionDelete) { result -> + result.fold( + onSuccess = {/*TODO: Should wait for acknowledgement and delete keys?*/ }, + onFailure = { error -> onFailure(error) } + ) + } + } + + internal fun respondSessionPayload(topic: String, jsonRpcResponse: JsonRpcResponse, onFailure: (Throwable) -> Unit) { + relayer.respond( + Topic(topic), jsonRpcResponse, { Logger.error("Session payload sent successfully") }, + { error -> + onFailure(error) + Logger.error("Sending session payload error: $error") + }) + } + + internal fun update( + topic: String, sessionState: EngineData.SessionState, + onSuccess: (Pair>) -> Unit, + onFailure: (Throwable) -> Unit + ) { + val sessionUpdate: PostSettlementSession.SessionUpdate = + PostSettlementSession.SessionUpdate(id = generateId(), params = Session.UpdateParams(SessionState(sessionState.accounts))) + storageRepository.updateSessionWithAccounts(topic, sessionState.accounts) + relayer.request(Topic(topic), sessionUpdate) { result -> + result.fold( + onSuccess = { onSuccess(Pair(topic, sessionState.accounts)) }, + onFailure = { error -> onFailure(error) } + ) + } + } + + internal fun upgrade( + topic: String, permissions: EngineData.SessionPermissions, + onSuccess: (Pair) -> Unit, + onFailure: (Throwable) -> Unit + ) { + val sessionUpgrade = PostSettlementSession.SessionUpgrade( + id = generateId(), + params = Session.SessionPermissionsParams(permissions = permissions.toSessionsPermissions()) + ) + storageRepository.updateSessionWithPermissions(topic, permissions.blockchain?.chains, permissions.jsonRpc?.methods) + relayer.request(Topic(topic), sessionUpgrade) { result -> + result.fold( + onSuccess = { onSuccess(Pair(topic, permissions)) }, + onFailure = { error -> onFailure(error) } + ) + } + } + + internal fun notify( + topic: String, notification: EngineData.Notification, + onSuccess: (String) -> Unit, + onFailure: (Throwable) -> Unit + ) { + /*TODO check whether under given topic there is a pairing or session stored and create proper Notification class*/ + //val pairingNotification = PostSettlementPairing.PairingNotification(id = generateId(), params = Pairing.NotificationParams(notification.type, notification.data)) + val sessionNotification = + PostSettlementSession + .SessionNotification(id = generateId(), params = Session.NotificationParams(notification.type, notification.data)) + relayer.request(Topic(topic), sessionNotification) { result -> + result.fold( + onSuccess = { onSuccess(topic) }, + onFailure = { error -> onFailure(error) } + ) + } + } + + internal fun ping(topic: String, onSuccess: (String) -> Unit, onFailure: (Throwable) -> Unit) { + /*TODO check whether under given topic there is a pairing or session stored and create proper Ping class*/ + //val pairingParams = PostSettlementPairing.PairingPing(id = generateId(), params = Pairing.PingParams()) + val sessionPing = PostSettlementSession.SessionPing(id = generateId(), params = Session.PingParams()) + relayer.request(Topic(topic), sessionPing) { result -> + result.fold( + onSuccess = { onSuccess(topic) }, + onFailure = { error -> onFailure(error) } + ) + } + } + + internal fun getListOfPendingSessions(): List { + return storageRepository.getListOfSessionVOs().filter { session -> + session.status == SequenceStatus.PENDING && session.expiry.isSequenceValid() + }.map { session -> + val (_, peerPublicKey) = crypto.getKeyAgreement(session.topic) + + EngineData.SessionProposal( + name = session.appMetaData?.name ?: String.Empty, + description = session.appMetaData?.description ?: String.Empty, + url = session.appMetaData?.url ?: String.Empty, + icons = session.appMetaData?.icons?.map { URI(it) } ?: emptyList(), + chains = session.chains, + methods = session.methods, + types = session.types, + topic = session.topic.value, + proposerPublicKey = peerPublicKey.keyAsHex, + ttl = session.ttl.seconds, + accounts = session.accounts + ) + } + } + + internal fun getListOfSettledSessions(): List = storageRepository.getListOfSessionVOs().filter { session -> + session.status == SequenceStatus.SETTLED && session.expiry.isSequenceValid() + }.map { session -> + val metadata: AppMetaData? = session.appMetaData?.let { appMetaData -> + AppMetaData(appMetaData.name, appMetaData.description, appMetaData.url, appMetaData.icons) + } + + EngineData.SettledSession( + session.topic.value, + session.accounts, + metadata, + EngineData.SettledSession.Permissions( + EngineData.SettledSession.Permissions.Blockchain(session.chains), + EngineData.SettledSession.Permissions.JsonRpc(session.methods), + EngineData.SettledSession.Permissions.Notifications(session.types) + ) + ) + } + + private fun collectClientSyncJsonRpc() = scope.launch { + relayer.clientSyncJsonRpc.collect { payload -> + when (payload.params) { + is Pairing.PayloadParams -> onPairingPayload(payload.params) + is Session.DeleteParams -> onSessionDelete(payload.params, payload.topic) + is Session.SessionPayloadParams -> onSessionPayload(payload.params, payload.topic, payload.requestId) + is Pairing.DeleteParams -> onPairingDelete(payload.params, payload.topic) + is Session.NotificationParams -> onSessionNotification(payload.params, payload.topic) + is Pairing.PingParams, is Session.PingParams -> onPing(payload.topic, payload.requestId) + } + } + } + + private fun onPing(topic: Topic, requestId: Long) { + val jsonRpcResult = JsonRpcResponse.JsonRpcResult(id = requestId, result = "true") + relayer.respond(topic, jsonRpcResult, + { Logger.log("Ping send successfully") }, + { error -> Logger.error("Ping Error: $error") }) + } + + private fun onPairingPayload(payload: Pairing.PayloadParams) { + val proposal = payload.request.params + storageRepository.insertSessionProposal(proposal, proposal.proposer.metadata, defaultSequenceExpirySeconds(), controllerType) + val (sharedKey, publicKey) = crypto.getKeyAgreement(proposal.signal.params.topic) + crypto.setEncryptionKeys(sharedKey as SharedKey, publicKey as PublicKey, proposal.topic) + _sequenceEvent.value = SequenceLifecycle.OnSessionProposal(proposal.toSessionProposal()) + } + + private fun onSessionPayload(payload: Session.SessionPayloadParams, topic: Topic, requestId: Long) { + //TODO Validate session request + add unmarshaling of generic session request payload to the usable generic object + val params = payload.request.params.toString() + val chainId = payload.chainId + val method = payload.request.method + _sequenceEvent.value = SequenceLifecycle.OnSessionRequest( + EngineData.SessionRequest(topic.value, chainId, EngineData.SessionRequest.JSONRPCRequest(requestId, method, params)) + ) + } + + private fun onSessionDelete(params: Session.DeleteParams, topic: Topic) { + crypto.removeKeys(topic.value) + storageRepository.deleteSession(topic.value) + relayer.unsubscribe(topic) + _sequenceEvent.value = SequenceLifecycle.OnSessionDeleted(EngineData.DeletedSession(topic.value, params.reason.message)) + } + + private fun onSessionNotification(params: Session.NotificationParams, topic: Topic) { + val type = params.type + val data = params.data.toString() + _sequenceEvent.value = SequenceLifecycle.OnSessionNotification(EngineData.SessionNotification(topic.value, type, data)) + } + + private fun onPairingDelete(params: Pairing.DeleteParams, topic: Topic) { + crypto.removeKeys(topic.value) + relayer.unsubscribe(topic) + //TODO delete from DB + } + + private fun settlePairingSequence( + relay: JSONObject, + selfPublicKey: PublicKey, + peerPublicKey: PublicKey, + controllerPublicKey: PublicKey, + expiry: Expiry + ): SettledPairingSequence { + val (_, settledTopic) = crypto.generateTopicAndSharedKey(selfPublicKey, peerPublicKey) + return SettledPairingSequence( + settledTopic, + relay, + selfPublicKey, + peerPublicKey, + PairingPermissions(PairingParticipant(controllerPublicKey.keyAsHex)), + expiry + ) + } + + private fun resubscribeToSettledPairings() { + val (listOfExpiredPairing, listOfValidPairing) = storageRepository.getListOfPairingVOs().partition { pairing -> !pairing.expiry.isSequenceValid() } + + listOfExpiredPairing + .map { pairing -> pairing.topic } + .onEach { pairingTopic -> + relayer.unsubscribe(pairingTopic) + storageRepository.deletePairing(pairingTopic.value) + } + + listOfValidPairing + .filter { pairing -> pairing.status == SequenceStatus.SETTLED } + .map { pairing -> pairing.topic } + .onEach { pairingTopic -> + relayer.subscribe(pairingTopic) + } + } + + private fun resubscribeToSettledSession() { + val (listOfExpiredSession, listOfValidSessions) = storageRepository.getListOfSessionVOs().partition { session -> !session.expiry.isSequenceValid() } + + listOfExpiredSession + .map { session -> session.topic } + .onEach { sessionTopic -> + relayer.unsubscribe(sessionTopic) + storageRepository.deleteSession(sessionTopic.value) + } + + listOfValidSessions + .filter { session -> session.status == SequenceStatus.SETTLED } + .onEach { session -> + relayer.subscribe(session.topic) + } + } + + private fun Expiry.isSequenceValid(): Boolean { + return seconds > (System.currentTimeMillis() / 1000) + } + + private fun defaultSequenceExpirySeconds() = ((System.currentTimeMillis() / 1000) + 86400) + + private fun settleSessionSequence( + relay: RelayProtocolOptions, + selfPublicKey: PublicKey, + peerPublicKey: PublicKey, + expiry: Expiry, + sessionState: SessionState + ): SettledSessionSequence { + val (sharedKey, topic) = crypto.generateTopicAndSharedKey(selfPublicKey, peerPublicKey) + return SettledSessionSequence( + topic, + relay, + selfPublicKey, + peerPublicKey, + SettledSessionPermissions(Controller(selfPublicKey.keyAsHex)), + sharedKey, + expiry, + sessionState + ) + } + + class EngineFactory( + val useTLs: Boolean = false, + val hostName: String, + val projectId: String, + val isController: Boolean, + val application: Application, + val metaData: AppMetaData + ) +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/model/EngineData.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/model/EngineData.kt new file mode 100644 index 0000000000..93a869aad5 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/model/EngineData.kt @@ -0,0 +1,80 @@ +package com.walletconnect.walletconnectv2.engine.model + +import com.walletconnect.walletconnectv2.common.AppMetaData +import java.net.URI + +sealed class EngineData { + + internal data class SessionProposal( + val name: String, + val description: String, + val url: String, + val icons: List, + val chains: List, + val methods: List, + val types: List, + val topic: String, + val proposerPublicKey: String, + val ttl: Long, + val accounts: List + ) : EngineData() { + val icon: String = icons.first().toString() + } + + internal data class SessionRequest( + val topic: String, + val chainId: String?, + val request: JSONRPCRequest + ) : EngineData() { + + data class JSONRPCRequest( + val id: Long, + val method: String, + val params: String + ) : EngineData() + } + + internal data class DeletedSession( + val topic: String, + val reason: String + ) : EngineData() + + internal data class SettledSession( + val topic: String, + val accounts: List, + val peerAppMetaData: AppMetaData?, + val permissions: Permissions + ) : EngineData() { + + data class Permissions( + val blockchain: Blockchain, + val jsonRpc: JsonRpc, + val notifications: Notifications + ) { + data class Blockchain(val chains: List) + + data class JsonRpc(val methods: List) + + data class Notifications(val types: List) + } + } + + internal data class SessionNotification( + val topic: String, + val type: String, + val data: String + ) : EngineData() + + internal data class Notification( + val type: String, + val data: String + ) : EngineData() + + data class SessionState(val accounts: List) : EngineData() + + data class SessionPermissions(val blockchain: Blockchain? = null, val jsonRpc: Jsonrpc? = null) : EngineData() + + data class Blockchain(val chains: List) : EngineData() + + data class Jsonrpc(val methods: List) : EngineData() +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/sequence/SequenceLifecycle.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/sequence/SequenceLifecycle.kt new file mode 100644 index 0000000000..06f8392108 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/engine/sequence/SequenceLifecycle.kt @@ -0,0 +1,11 @@ +package com.walletconnect.walletconnectv2.engine.sequence + +import com.walletconnect.walletconnectv2.engine.model.EngineData + +internal sealed class SequenceLifecycle { + class OnSessionProposal(val proposal: EngineData.SessionProposal) : SequenceLifecycle() + class OnSessionRequest(val request: EngineData.SessionRequest) : SequenceLifecycle() + class OnSessionDeleted(val deletedSession: EngineData.DeletedSession) : SequenceLifecycle() + class OnSessionNotification(val notification: EngineData.SessionNotification) : SequenceLifecycle() + object Default : SequenceLifecycle() +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/errors/Exceptions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/errors/Exceptions.kt new file mode 100644 index 0000000000..b83ac79d9d --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/errors/Exceptions.kt @@ -0,0 +1,7 @@ +package com.walletconnect.walletconnectv2.errors + +sealed class WalletConnectExceptions(override val message: String?) : Exception(message) { + class ProjectIdDoesNotExistException(override val message: String?) : WalletConnectExceptions(message) + class InvalidProjectIdException(override val message: String?) : WalletConnectExceptions(message) + class ServerException(override val message: String?) : WalletConnectExceptions(message) +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/errors/ExceptionsHanlder.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/errors/ExceptionsHanlder.kt similarity index 50% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/errors/ExceptionsHanlder.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/errors/ExceptionsHanlder.kt index c2d5011a22..47ec535758 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/errors/ExceptionsHanlder.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/errors/ExceptionsHanlder.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.errors +package com.walletconnect.walletconnectv2.errors import java.net.HttpURLConnection @@ -6,8 +6,8 @@ val Throwable.exception: Throwable get() = when { this.message?.contains(HttpURLConnection.HTTP_UNAUTHORIZED.toString()) == true -> - ApiKeyDoesNotExistException(this.message) + WalletConnectExceptions.ProjectIdDoesNotExistException(this.message) this.message?.contains(HttpURLConnection.HTTP_FORBIDDEN.toString()) == true -> - InvalidApiKeyException(this.message) - else -> ServerException(this.message) + WalletConnectExceptions.InvalidProjectIdException(this.message) + else -> WalletConnectExceptions.ServerException(this.message) } \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/JsonRpcSerializer.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/JsonRpcSerializer.kt new file mode 100644 index 0000000000..0d1828cb34 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/JsonRpcSerializer.kt @@ -0,0 +1,106 @@ +package com.walletconnect.walletconnectv2.jsonrpc + +import com.walletconnect.walletconnectv2.ClientParams +import com.walletconnect.walletconnectv2.clientsync.ClientSyncJsonRpc +import com.walletconnect.walletconnectv2.clientsync.pairing.after.PostSettlementPairing +import com.walletconnect.walletconnectv2.clientsync.pairing.before.PreSettlementPairing +import com.walletconnect.walletconnectv2.clientsync.session.after.PostSettlementSession +import com.walletconnect.walletconnectv2.clientsync.session.before.PreSettlementSession +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.crypto.CryptoManager +import com.walletconnect.walletconnectv2.crypto.codec.AuthenticatedEncryptionCodec +import com.walletconnect.walletconnectv2.crypto.data.EncryptionPayload +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey +import com.walletconnect.walletconnectv2.crypto.managers.BouncyCastleCryptoManager +import com.walletconnect.walletconnectv2.jsonrpc.model.JsonRpcResponse +import com.walletconnect.walletconnectv2.jsonrpc.utils.JsonRpcMethod +import com.walletconnect.walletconnectv2.moshi +import com.walletconnect.walletconnectv2.util.Empty +import com.walletconnect.walletconnectv2.util.hexToUtf8 + +class JsonRpcSerializer { + + private val codec: AuthenticatedEncryptionCodec = AuthenticatedEncryptionCodec() + private val crypto: CryptoManager = BouncyCastleCryptoManager() + + fun serialize(payload: ClientSyncJsonRpc, topic: Topic): String { + val json = serialize(payload) + val (sharedKey, selfPublic) = crypto.getKeyAgreement(topic) + + return if (sharedKey.keyAsHex.isEmpty() || selfPublic.keyAsHex.isEmpty()) { + json.encode() + } else { + codec.encrypt(json, sharedKey as SharedKey, selfPublic as PublicKey) + } + } + + fun decode(message: String, topic: Topic): String { + val (sharedKey, selfPublic) = crypto.getKeyAgreement(topic) + return if (sharedKey.keyAsHex.isEmpty() || selfPublic.keyAsHex.isEmpty()) { + message.hexToUtf8 + } else { + codec.decrypt(toEncryptionPayload(message), sharedKey as SharedKey) + } + } + + fun deserialize(method: String, json: String): ClientParams? = + when (method) { + JsonRpcMethod.WC_PAIRING_APPROVE -> tryDeserialize(json)?.params + JsonRpcMethod.WC_PAIRING_REJECT -> tryDeserialize(json)?.params + JsonRpcMethod.WC_PAIRING_PAYLOAD -> tryDeserialize(json)?.params + JsonRpcMethod.WC_PAIRING_UPDATE -> tryDeserialize(json)?.params + JsonRpcMethod.WC_PAIRING_PING -> tryDeserialize(json)?.params + JsonRpcMethod.WC_PAIRING_NOTIFICATION -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_APPROVE -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_REJECT -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_PROPOSE -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_PAYLOAD -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_DELETE -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_UPDATE -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_UPGRADE -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_PING -> tryDeserialize(json)?.params + JsonRpcMethod.WC_SESSION_NOTIFICATION -> tryDeserialize(json)?.params + else -> null + } + + inline fun tryDeserialize(json: String): T? = runCatching { moshi.adapter(T::class.java).fromJson(json) }.getOrNull() + + inline fun trySerialize(type: T): String = moshi.adapter(T::class.java).toJson(type) + + private fun serialize(payload: ClientSyncJsonRpc): String = + when (payload) { + is PreSettlementPairing.Approve -> trySerialize(payload) + is PreSettlementPairing.Reject -> trySerialize(payload) + is PostSettlementPairing.PairingPayload -> trySerialize(payload) + is PostSettlementPairing.PairingNotification -> trySerialize(payload) + is PostSettlementPairing.PairingPing -> trySerialize(payload) + is PostSettlementPairing.PairingUpdate -> trySerialize(payload) + is PreSettlementSession.Approve -> trySerialize(payload) + is PreSettlementSession.Reject -> trySerialize(payload) + is PreSettlementSession.Proposal -> trySerialize(payload) + is PostSettlementSession.SessionNotification -> trySerialize(payload) + is PostSettlementSession.SessionPing -> trySerialize(payload) + is PostSettlementSession.SessionUpdate -> trySerialize(payload) + is PostSettlementSession.SessionUpgrade -> trySerialize(payload) + is PostSettlementSession.SessionPayload -> trySerialize(payload) + is PostSettlementSession.SessionDelete -> trySerialize(payload) + is JsonRpcResponse -> trySerialize(payload) + else -> String.Empty + } + + private fun toEncryptionPayload(message: String): EncryptionPayload { + val pubKeyStartIndex = EncryptionPayload.ivLength + val macStartIndex = pubKeyStartIndex + EncryptionPayload.publicKeyLength + val cipherTextStartIndex = macStartIndex + EncryptionPayload.macLength + + val iv = message.substring(0, pubKeyStartIndex) + val publicKey = message.substring(pubKeyStartIndex, macStartIndex) + val mac = message.substring(macStartIndex, cipherTextStartIndex) + val cipherText = message.substring(cipherTextStartIndex, message.length) + + return EncryptionPayload(iv, publicKey, mac, cipherText) + } + + private fun String.encode(): String = this.encodeToByteArray().joinToString(separator = "") { bytes -> String.format("%02X", bytes) } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/history/JsonRpcHistory.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/history/JsonRpcHistory.kt new file mode 100644 index 0000000000..1fce575225 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/history/JsonRpcHistory.kt @@ -0,0 +1,45 @@ +package com.walletconnect.walletconnectv2.jsonrpc.history + +import android.annotation.SuppressLint +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys +import com.walletconnect.walletconnectv2.app +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.util.Logger + +class JsonRpcHistory { + //Region: Move to DI + // TODO: updated based on https://stackoverflow.com/a/63357267 + private val sharedPreferences: SharedPreferences + get() = EncryptedSharedPreferences.create( + sharedPrefsFile, + mainKeyAlias, + app.applicationContext, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + //End of region + + @SuppressLint("ApplySharedPref") + fun setRequest(requestId: Long, topic: Topic): Boolean { + return if (!sharedPreferences.contains(requestId.toString())) { + sharedPreferences.edit().putString(requestId.toString(), topic.value).commit() + } else { + Logger.log("Duplicated JsonRpc RequestId: $requestId\tTopic: ${topic.value}") + false + } + } + + fun deleteRequests(topic: Topic) { + sharedPreferences.all.entries + .filter { entry -> entry.value == topic.value } + .forEach { entry -> sharedPreferences.edit().remove(entry.key).apply() } + } + + companion object { + private const val sharedPrefsFile: String = "wc_rpc_store" + private val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC + private val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/model/JsonRpcResponse.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/model/JsonRpcResponse.kt new file mode 100644 index 0000000000..b08cbd6ed3 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/model/JsonRpcResponse.kt @@ -0,0 +1,27 @@ +package com.walletconnect.walletconnectv2.jsonrpc.model + +import com.squareup.moshi.JsonClass +import com.walletconnect.walletconnectv2.clientsync.ClientSyncJsonRpc + +sealed class JsonRpcResponse : ClientSyncJsonRpc { + + abstract override val id: Long + val jsonrpc: String = "2.0" + + @JsonClass(generateAdapter = true) + data class JsonRpcResult( + override val id: Long, + val result: String + ) : JsonRpcResponse() + + @JsonClass(generateAdapter = true) + data class JsonRpcError( + override val id: Long, + val error: Error, + ) : JsonRpcResponse() + + data class Error( + val code: Long, + val message: String, + ) +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/model/WCRequestSubscriptionPayload.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/model/WCRequestSubscriptionPayload.kt new file mode 100644 index 0000000000..6cd6a3838c --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/model/WCRequestSubscriptionPayload.kt @@ -0,0 +1,17 @@ +package com.walletconnect.walletconnectv2.jsonrpc.model + +import com.walletconnect.walletconnectv2.ClientParams +import com.walletconnect.walletconnectv2.common.Topic + +data class WCRequestSubscriptionPayload( + val requestId: Long, + val topic: Topic, + val method: String, + val params: ClientParams +) + +data class ClientJsonRpc( + val id: Long, + val jsonrpc: String, + val method: String +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/utils/JsonRpcMethod.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/utils/JsonRpcMethod.kt new file mode 100644 index 0000000000..04dc631635 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/jsonrpc/utils/JsonRpcMethod.kt @@ -0,0 +1,21 @@ +package com.walletconnect.walletconnectv2.jsonrpc.utils + +object JsonRpcMethod { + const val WC_PAIRING_PAYLOAD: String = "wc_pairingPayload" + const val WC_PAIRING_APPROVE: String = "wc_pairingApprove" + const val WC_PAIRING_REJECT: String = "wc_pairingReject" + const val WC_PAIRING_UPDATE: String = "wc_pairingUpdate" + const val WC_PAIRING_PING: String = "wc_pairingPing" + const val WC_PAIRING_DELETE: String = "wc_pairingDelete" + const val WC_PAIRING_NOTIFICATION: String = "wc_pairingNotification" + + const val WC_SESSION_PAYLOAD: String = "wc_sessionPayload" + const val WC_SESSION_PROPOSE: String = "wc_sessionPropose" + const val WC_SESSION_APPROVE: String = "wc_sessionApprove" + const val WC_SESSION_UPDATE: String = "wc_sessionUpdate" + const val WC_SESSION_UPGRADE: String = "wc_sessionUpgrade" + const val WC_SESSION_REJECT: String = "wc_sessionReject" + const val WC_SESSION_DELETE: String = "wc_sessionDelete" + const val WC_SESSION_PING: String = "wc_sessionPing" + const val WC_SESSION_NOTIFICATION: String = "wc_sessionNotification" +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/data/model/Relay.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/Relay.kt similarity index 66% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/data/model/Relay.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/Relay.kt index d2f16c2741..c30e5d847e 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/data/model/Relay.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/Relay.kt @@ -1,20 +1,19 @@ -package org.walletconnect.walletconnectv2.relay.data.model +package com.walletconnect.walletconnectv2.relay.waku import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.common.SubscriptionId -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.common.Ttl -import org.walletconnect.walletconnectv2.common.network.adapters.SubscriptionIdAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.TtlAdapter - -// TODO: Maybe look into separating children into different files +import com.walletconnect.walletconnectv2.common.SubscriptionId +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.common.network.adapters.SubscriptionIdAdapter +import com.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter +import com.walletconnect.walletconnectv2.common.network.adapters.TtlAdapter + sealed class Relay { abstract val id: Long abstract val jsonrpc: String - sealed class Publish: Relay() { + sealed class Publish : Relay() { @JsonClass(generateAdapter = true) data class Request( @@ -26,7 +25,7 @@ sealed class Relay { val method: String = "waku_publish", @Json(name = "params") val params: Params - ): Publish() { + ) : Publish() { @JsonClass(generateAdapter = true) data class Params( @@ -48,10 +47,19 @@ sealed class Relay { override val jsonrpc: String = "2.0", @Json(name = "result") val result: Boolean - ): Publish() + ) : Publish() + + data class JsonRpcError( + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "error") + val error: Error, + @Json(name = "id") + override val id: Long + ) : Publish() } - sealed class Subscribe: Relay() { + sealed class Subscribe : Relay() { @JsonClass(generateAdapter = true) data class Request( @@ -63,7 +71,7 @@ sealed class Relay { val method: String = "waku_subscribe", @Json(name = "params") val params: Params - ): Subscribe() { + ) : Subscribe() { @JsonClass(generateAdapter = true) data class Params( @@ -81,10 +89,19 @@ sealed class Relay { @Json(name = "result") @field:SubscriptionIdAdapter.Qualifier val result: SubscriptionId - ): Subscribe() + ) : Subscribe() + + data class JsonRpcError( + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "error") + val error: Error, + @Json(name = "id") + override val id: Long + ) : Subscribe() } - sealed class Subscription: Relay() { + sealed class Subscription : Relay() { @JsonClass(generateAdapter = true) data class Request( @@ -96,7 +113,10 @@ sealed class Relay { val method: String = "waku_subscription", @Json(name = "params") val params: Params - ): Subscription() { + ) : Subscription() { + + val subscriptionTopic: Topic = params.subscriptionData.topic + val message: String = params.subscriptionData.message @JsonClass(generateAdapter = true) data class Params( @@ -125,10 +145,19 @@ sealed class Relay { override val jsonrpc: String = "2.0", @Json(name = "result") val result: Boolean - ): Subscription() + ) : Subscription() + + data class JsonRpcError( + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "error") + val error: Error, + @Json(name = "id") + override val id: Long + ) : Subscription() } - sealed class Unsubscribe: Relay() { + sealed class Unsubscribe : Relay() { data class Request( @Json(name = "id") @@ -139,7 +168,7 @@ sealed class Relay { val method: String = "waku_unsubscribe", @Json(name = "params") val params: Params - ): Unsubscribe() { + ) : Unsubscribe() { data class Params( @Json(name = "topic") @@ -158,6 +187,24 @@ sealed class Relay { override val jsonrpc: String = "2.0", @Json(name = "result") val result: Boolean - ): Unsubscribe() + ) : Unsubscribe() + + data class JsonRpcError( + @Json(name = "jsonrpc") + override val jsonrpc: String = "2.0", + @Json(name = "error") + val error: Error, + @Json(name = "id") + override val id: Long + ) : Unsubscribe() + } + + data class Error( + @Json(name = "code") + val code: Long, + @Json(name = "message") + val message: String, + ) { + val errorMessage: String = "Error code: $code; Error message: $message" } } \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/data/RelayService.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/RelayService.kt similarity index 68% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/data/RelayService.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/RelayService.kt index 38bda9c826..8546392e8d 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/data/RelayService.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/RelayService.kt @@ -1,10 +1,9 @@ -package org.walletconnect.walletconnectv2.relay.data +package com.walletconnect.walletconnectv2.relay.waku import com.tinder.scarlet.WebSocket import com.tinder.scarlet.ws.Receive import com.tinder.scarlet.ws.Send import kotlinx.coroutines.flow.Flow -import org.walletconnect.walletconnectv2.relay.data.model.Relay interface RelayService { @@ -17,6 +16,9 @@ interface RelayService { @Receive fun observePublishAcknowledgement(): Flow + @Receive + fun observePublishError(): Flow + @Send fun subscribeRequest(subscribeRequest: Relay.Subscribe.Request) @@ -24,17 +26,20 @@ interface RelayService { fun observeSubscribeAcknowledgement(): Flow @Receive - fun observeSubscriptionRequest(): Flow + fun observeSubscribeError(): Flow - @Send - fun publishSubscriptionAcknowledgment(publishRequest: Relay.Subscription.Acknowledgement) + @Receive + fun observeSubscriptionRequest(): Flow @Send - fun subscriptionAcknowledgement(subscriptionAcknowledgement: Relay.Subscription.Acknowledgement) + fun publishSubscriptionAcknowledgement(publishRequest: Relay.Subscription.Acknowledgement) @Send fun unsubscribeRequest(unsubscribeRequest: Relay.Unsubscribe.Request) @Receive fun observeUnsubscribeAcknowledgement(): Flow + + @Receive + fun observeUnsubscribeError(): Flow } \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/WakuNetworkRepository.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/WakuNetworkRepository.kt new file mode 100644 index 0000000000..7545653636 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/waku/WakuNetworkRepository.kt @@ -0,0 +1,185 @@ +package com.walletconnect.walletconnectv2.relay.waku + +import android.app.Application +import com.tinder.scarlet.Scarlet +import com.tinder.scarlet.WebSocket +import com.tinder.scarlet.lifecycle.android.AndroidLifecycle +import com.tinder.scarlet.messageadapter.moshi.MoshiMessageAdapter +import com.tinder.scarlet.retry.LinearBackoffStrategy +import com.tinder.scarlet.websocket.okhttp.newWebSocketFactory +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import okhttp3.OkHttpClient +import com.walletconnect.walletconnectv2.common.SubscriptionId +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.moshi +import com.walletconnect.walletconnectv2.scope +import com.walletconnect.walletconnectv2.util.Logger +import com.walletconnect.walletconnectv2.util.adapters.FlowStreamAdapter +import com.walletconnect.walletconnectv2.util.generateId +import java.util.concurrent.TimeUnit + +class WakuNetworkRepository internal constructor( + private val useTLs: Boolean, + private val hostName: String, + private val projectId: String, + private val application: Application +) { + //region Move to DI module + private val okHttpClient = OkHttpClient.Builder() + .writeTimeout(TIMEOUT_TIME, TimeUnit.MILLISECONDS) + .readTimeout(TIMEOUT_TIME, TimeUnit.MILLISECONDS) + .callTimeout(TIMEOUT_TIME, TimeUnit.MILLISECONDS) + .connectTimeout(TIMEOUT_TIME, TimeUnit.MILLISECONDS) + .build() + + private val scarlet by lazy { + Scarlet.Builder() + .backoffStrategy(LinearBackoffStrategy(TimeUnit.MINUTES.toMillis(DEFAULT_BACKOFF_MINUTES))) + .webSocketFactory(okHttpClient.newWebSocketFactory(getServerUrl())) + .lifecycle(AndroidLifecycle.ofApplicationForeground(application)) // TODO: Maybe have debug version of scarlet w/o application and release version of scarlet w/ application once DI is setup + .addMessageAdapterFactory(MoshiMessageAdapter.Factory(moshi)) + .addStreamAdapterFactory(FlowStreamAdapter.Factory()) + .build() + } + private val relay: RelayService by lazy { scarlet.create(RelayService::class.java) } + //endregion + + internal val eventsFlow: SharedFlow = relay.eventsFlow().shareIn(scope, SharingStarted.Lazily, REPLAY) + internal val observePublishAcknowledgement: Flow = relay.observePublishAcknowledgement() + + internal val subscriptionRequest: Flow = + relay.observeSubscriptionRequest() + .onEach { relayRequest -> supervisorScope { publishSubscriptionAcknowledgement(relayRequest.id) } } + + fun publish(topic: Topic, message: String, onResult: (Result) -> Unit = {}) { + val publishRequest = + Relay.Publish.Request(id = generateId(), params = Relay.Publish.Request.Params(topic = topic, message = message)) + observePublishAcknowledgement { acknowledgement -> onResult(Result.success(acknowledgement)) } + observePublishError { error -> onResult(Result.failure(error)) } + relay.publishRequest(publishRequest) + } + + fun subscribe(topic: Topic, onResult: (Result) -> Unit) { + val subscribeRequest = Relay.Subscribe.Request(id = generateId(), params = Relay.Subscribe.Request.Params(topic)) + observeSubscribeAcknowledgement { acknowledgement -> onResult(Result.success(acknowledgement)) } + observeSubscribeError { error -> onResult(Result.failure(error)) } + relay.subscribeRequest(subscribeRequest) + } + + fun unsubscribe(topic: Topic, subscriptionId: SubscriptionId, onResult: (Result) -> Unit) { + val unsubscribeRequest = + Relay.Unsubscribe.Request(id = generateId(), params = Relay.Unsubscribe.Request.Params(topic, subscriptionId)) + observeUnSubscribeAcknowledgement { acknowledgement -> onResult(Result.success(acknowledgement)) } + observeUnSubscribeError { error -> onResult(Result.failure(error)) } + relay.unsubscribeRequest(unsubscribeRequest) + } + + private fun publishSubscriptionAcknowledgement(id: Long) { + val publishRequest = Relay.Subscription.Acknowledgement(id = id, result = true) + relay.publishSubscriptionAcknowledgement(publishRequest) + } + + private fun observePublishAcknowledgement(onResult: (Relay.Publish.Acknowledgement) -> Unit) { + scope.launch { + relay.observePublishAcknowledgement() + .catch { exception -> Logger.error(exception) } + .collect { acknowledgement -> + supervisorScope { + onResult(acknowledgement) + cancel() + } + } + } + } + + private fun observePublishError(onFailure: (Throwable) -> Unit) { + scope.launch { + relay.observePublishError() + .onEach { jsonRpcError -> Logger.error(Throwable(jsonRpcError.error.errorMessage)) } + .catch { exception -> Logger.error(exception) } + .collect { errorResponse -> + supervisorScope { + onFailure(Throwable(errorResponse.error.errorMessage)) + cancel() + } + } + } + } + + private fun observeSubscribeAcknowledgement(onResult: (Relay.Subscribe.Acknowledgement) -> Unit) { + scope.launch { + relay.observeSubscribeAcknowledgement() + .catch { exception -> Logger.error(exception) } + .collect { acknowledgement -> + supervisorScope { + onResult(acknowledgement) + cancel() + } + } + } + } + + private fun observeSubscribeError(onFailure: (Throwable) -> Unit) { + scope.launch { + relay.observeSubscribeError() + .onEach { jsonRpcError -> Logger.error(Throwable(jsonRpcError.error.errorMessage)) } + .catch { exception -> Logger.error(exception) } + .collect { errorResponse -> + supervisorScope { + onFailure(Throwable(errorResponse.error.errorMessage)) + cancel() + } + } + } + } + + private fun observeUnSubscribeAcknowledgement(onSuccess: (Relay.Unsubscribe.Acknowledgement) -> Unit) { + scope.launch { + relay.observeUnsubscribeAcknowledgement() + .catch { exception -> Logger.error(exception) } + .collect { acknowledgement -> + supervisorScope { + onSuccess(acknowledgement) + cancel() + } + } + } + } + + private fun observeUnSubscribeError(onFailure: (Throwable) -> Unit) { + scope.launch { + relay.observeUnsubscribeError() + .onEach { jsonRpcError -> Logger.error(Throwable(jsonRpcError.error.errorMessage)) } + .catch { exception -> Logger.error(exception) } + .collect { errorResponse -> + supervisorScope { + onFailure(Throwable(errorResponse.error.errorMessage)) + cancel() + } + } + } + } + + private fun getServerUrl(): String = + ((if (useTLs) "wss" else "ws") + "://$hostName/?projectId=$projectId").trim() + + class WakuNetworkFactory( + val useTls: Boolean, + val hostName: String, + val projectId: String, + val application: Application + ) + + companion object { + private const val TIMEOUT_TIME: Long = 5000L + private const val DEFAULT_BACKOFF_MINUTES: Long = 5L + private const val REPLAY: Int = 1 + + fun init(wakuNetworkFactory: WakuNetworkFactory) = with(wakuNetworkFactory) { + WakuNetworkRepository(useTls, hostName, projectId, application) + } + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/walletconnect/WalletConnectRelayer.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/walletconnect/WalletConnectRelayer.kt new file mode 100644 index 0000000000..279f70c11c --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/relay/walletconnect/WalletConnectRelayer.kt @@ -0,0 +1,171 @@ +package com.walletconnect.walletconnectv2.relay.walletconnect + +import android.app.Application +import com.tinder.scarlet.WebSocket +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import com.walletconnect.walletconnectv2.clientsync.ClientSyncJsonRpc +import com.walletconnect.walletconnectv2.common.SubscriptionId +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.toWakuNetworkInitParams +import com.walletconnect.walletconnectv2.errors.exception +import com.walletconnect.walletconnectv2.jsonrpc.JsonRpcSerializer +import com.walletconnect.walletconnectv2.jsonrpc.history.JsonRpcHistory +import com.walletconnect.walletconnectv2.jsonrpc.model.ClientJsonRpc +import com.walletconnect.walletconnectv2.jsonrpc.model.JsonRpcResponse +import com.walletconnect.walletconnectv2.jsonrpc.model.WCRequestSubscriptionPayload +import com.walletconnect.walletconnectv2.relay.waku.Relay +import com.walletconnect.walletconnectv2.relay.waku.WakuNetworkRepository +import com.walletconnect.walletconnectv2.scope +import com.walletconnect.walletconnectv2.util.Logger + +class WalletConnectRelayer { + //Region: Move to DI + private lateinit var networkRepository: WakuNetworkRepository + private val serializer: JsonRpcSerializer = JsonRpcSerializer() + //end + + private val _clientSyncJsonRpc: MutableSharedFlow = MutableSharedFlow() + val clientSyncJsonRpc: SharedFlow = _clientSyncJsonRpc + + private val peerResponse: MutableSharedFlow = MutableSharedFlow() + + private val subscriptions: MutableMap = mutableMapOf() + private val jsonRpcHistory: JsonRpcHistory = JsonRpcHistory() + val isConnectionOpened = MutableStateFlow(false) + + internal fun initialize(relay: RelayFactory) { + networkRepository = WakuNetworkRepository.init(relay.toWakuNetworkInitParams()) + handleInitialisationErrors() + manageSubscriptions() + } + + fun request(topic: Topic, payload: ClientSyncJsonRpc, onResult: (Result) -> Unit) { + require(::networkRepository.isInitialized) + + if (jsonRpcHistory.setRequest(payload.id, topic)) { + scope.launch { + supervisorScope { + peerResponse + .filter { response -> response.id == payload.id } + .collect { response -> + when (response) { + is JsonRpcResponse.JsonRpcResult -> onResult(Result.success(response)) + is JsonRpcResponse.JsonRpcError -> onResult(Result.failure(Throwable(response.error.message))) + } + cancel() + } + } + } + + networkRepository.publish(topic, serializer.serialize(payload, topic)) { result -> + result.fold( + onSuccess = {}, + onFailure = { error -> onResult(Result.failure(error)) } + ) + } + } + } + + fun respond(topic: Topic, response: JsonRpcResponse, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) { + require(::networkRepository.isInitialized) + + networkRepository.publish(topic, serializer.serialize(response, topic)) { result -> + result.fold( + onSuccess = { onSuccess() }, + onFailure = { error -> onFailure(error) } + ) + } + } + + fun subscribe(topic: Topic) { + require(::networkRepository.isInitialized) + + networkRepository.subscribe(topic) { result -> + result.fold( + onSuccess = { acknowledgement -> subscriptions[topic.value] = acknowledgement.result.id }, + onFailure = { error -> Logger.error("Subscribe to topic: $topic error: $error") } + ) + } + } + + fun unsubscribe(topic: Topic) { + require(::networkRepository.isInitialized) + + if (subscriptions.contains(topic.value)) { + val subscriptionId = SubscriptionId(subscriptions[topic.value].toString()) + networkRepository.unsubscribe(topic, subscriptionId) { result -> + result.fold( + onSuccess = { + jsonRpcHistory.deleteRequests(topic) + subscriptions.remove(topic.value) + }, + onFailure = { error -> Logger.error("Unsubscribe to topic: $topic error: $error") } + ) + } + } + } + + private fun handleInitialisationErrors() { + scope.launch(exceptionHandler) { + networkRepository.eventsFlow + .onEach { event -> Logger.log("$event") } + .onEach { event: WebSocket.Event -> + if (event is WebSocket.Event.OnConnectionOpened<*>) { + isConnectionOpened.compareAndSet(expect = false, update = true) + } else if (event is WebSocket.Event.OnConnectionClosed) { + isConnectionOpened.compareAndSet(expect = true, update = false) + } + } + .filterIsInstance() + .collect { event -> + Logger.error(event.throwable.stackTraceToString()) + throw event.throwable.exception + } + } + } + + private fun manageSubscriptions() { + scope.launch(exceptionHandler) { + networkRepository.subscriptionRequest + .map { relayRequest -> + val decodedMessage = serializer.decode(relayRequest.message, relayRequest.subscriptionTopic) + val topic = relayRequest.subscriptionTopic + Pair(decodedMessage, topic) + } + .collect { (decryptedMessage, topic) -> + handleSessionRequest(decryptedMessage, topic) + handleJsonRpcResponse(decryptedMessage) + } + } + } + + private suspend fun handleSessionRequest(decryptedMessage: String, topic: Topic) { + val clientJsonRpc = serializer.tryDeserialize(decryptedMessage) + + if (clientJsonRpc != null && jsonRpcHistory.setRequest(clientJsonRpc.id, topic)) { + serializer.deserialize(clientJsonRpc.method, decryptedMessage)?.let { params -> + _clientSyncJsonRpc.emit(WCRequestSubscriptionPayload(clientJsonRpc.id, topic, clientJsonRpc.method, params)) + } + } + } + + private suspend fun handleJsonRpcResponse(decryptedMessage: String) { + val acknowledgement = serializer.tryDeserialize(decryptedMessage) + if (acknowledgement != null) { + peerResponse.emit(JsonRpcResponse.JsonRpcResult(acknowledgement.id, acknowledgement.result.toString())) + } + + val error = serializer.tryDeserialize(decryptedMessage) + if (error != null) { + peerResponse.emit(JsonRpcResponse.JsonRpcError(error.id, JsonRpcResponse.Error(error.error.code, error.error.message))) + } + } + + private val exceptionHandler = CoroutineExceptionHandler { _, exception -> Logger.error(exception) } + + class RelayFactory(val useTls: Boolean, val hostName: String, val projectId: String, val application: Application) +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/KeyChain.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/KeyChain.kt new file mode 100644 index 0000000000..e0c85971fb --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/KeyChain.kt @@ -0,0 +1,54 @@ +package com.walletconnect.walletconnectv2.storage + +import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKeys +import com.walletconnect.walletconnectv2.app +import com.walletconnect.walletconnectv2.crypto.data.Key +import com.walletconnect.walletconnectv2.util.Empty +import com.walletconnect.walletconnectv2.util.bytesToHex +import com.walletconnect.walletconnectv2.util.hexToBytes + +class KeyChain : KeyStore { + + //Region: Move to DI + // TODO: updated based on https://stackoverflow.com/a/63357267 + private val sharedPreferences: SharedPreferences + get() = EncryptedSharedPreferences.create( + sharedPrefsFile, + mainKeyAlias, + app.applicationContext, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + //End of region + + override fun setKey(tag: String, key1: Key, key2: Key) { + val keys = concatKeys(key1, key2) + sharedPreferences.edit().putString(tag, keys).apply() + } + + override fun getKeys(tag: String): Pair { + val concatKeys = sharedPreferences.getString(tag, String.Empty) ?: String.Empty + return splitKeys(concatKeys) + } + + override fun deleteKeys(tag: String) { + sharedPreferences.edit().remove(tag).apply() + } + + private fun concatKeys(keyA: Key, keyB: Key): String = (keyA.keyAsHex.hexToBytes() + keyB.keyAsHex.hexToBytes()).bytesToHex() + + private fun splitKeys(concatKeys: String): Pair { + val concatKeysByteArray = concatKeys.hexToBytes() + val privateKeyByteArray = concatKeysByteArray.sliceArray(0 until (concatKeysByteArray.size / 2)) + val publicKeyByteArray = concatKeysByteArray.sliceArray((concatKeysByteArray.size / 2) until concatKeysByteArray.size) + return privateKeyByteArray.bytesToHex() to publicKeyByteArray.bytesToHex() + } + + companion object { + private const val sharedPrefsFile: String = "wc_key_store" + private val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC + private val mainKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/KeyStore.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/KeyStore.kt new file mode 100644 index 0000000000..175a8e8ac7 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/KeyStore.kt @@ -0,0 +1,9 @@ +package com.walletconnect.walletconnectv2.storage + +import com.walletconnect.walletconnectv2.crypto.data.Key + +interface KeyStore { + fun setKey(tag: String, key1: Key, key2: Key) + fun getKeys(tag: String): Pair + fun deleteKeys(tag: String) +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/SequenceStatus.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/SequenceStatus.kt new file mode 100644 index 0000000000..acc21e6b48 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/SequenceStatus.kt @@ -0,0 +1,5 @@ +package com.walletconnect.walletconnectv2.storage + +enum class SequenceStatus { + PENDING, SETTLED +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/StorageRepository.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/StorageRepository.kt new file mode 100644 index 0000000000..8a3434796d --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/StorageRepository.kt @@ -0,0 +1,183 @@ +package com.walletconnect.walletconnectv2.storage + +import android.app.Application +import com.squareup.sqldelight.ColumnAdapter +import com.squareup.sqldelight.EnumColumnAdapter +import com.squareup.sqldelight.android.AndroidSqliteDriver +import com.squareup.sqldelight.db.SqlDriver +import com.squareup.sqldelight.runtime.coroutines.* +import com.walletconnect.walletconnectv2.Database +import com.walletconnect.walletconnectv2.clientsync.session.Session +import com.walletconnect.walletconnectv2.common.* +import com.walletconnect.walletconnectv2.storage.data.vo.AppMetaDataVO +import com.walletconnect.walletconnectv2.storage.data.vo.PairingVO +import com.walletconnect.walletconnectv2.storage.data.vo.SessionVO +import org.walletconnect.walletconnectv2.storage.data.dao.MetaDataDao +import org.walletconnect.walletconnectv2.storage.data.dao.PairingDao +import org.walletconnect.walletconnectv2.storage.data.dao.SessionDao + +internal class StorageRepository constructor(sqliteDriver: SqlDriver?, application: Application) { + //region provide with DI + // TODO: once DI is setup, replace var with val + private val driver = sqliteDriver ?: AndroidSqliteDriver( + schema = Database.Schema, + context = application, + name = "WalletConnectV2.db" + ) + private val sessionDatabase: Database = Database( + driver, + PairingDaoAdapter = PairingDao.Adapter( + statusAdapter = EnumColumnAdapter(), + controller_typeAdapter = EnumColumnAdapter() + ), + SessionDaoAdapter = SessionDao.Adapter( + permissions_chainsAdapter = listOfStringsAdapter, + permissions_methodsAdapter = listOfStringsAdapter, + permissions_typesAdapter = listOfStringsAdapter, + accountsAdapter = listOfStringsAdapter, + statusAdapter = EnumColumnAdapter(), + controller_typeAdapter = EnumColumnAdapter() + ), + MetaDataDaoAdapter = MetaDataDao.Adapter(iconsAdapter = listOfStringsAdapter) + ) + //endregion + + fun getListOfPairingVOs() = + sessionDatabase.pairingDaoQueries.getListOfPairingDaos(mapper = this@StorageRepository::mapPairingDaoToPairingVO).executeAsList() + + fun getListOfSessionVOs() = + sessionDatabase.sessionDaoQueries.getListOfSessionDaos(mapper = this@StorageRepository::mapSessionDaoToSessionVO).executeAsList() + + fun insertPairingProposal(topic: String, uri: String, expirySeconds: Long, sequenceStatus: SequenceStatus, controllerType: ControllerType) { + sessionDatabase.pairingDaoQueries.insertPairing(topic, uri, expirySeconds, sequenceStatus, controllerType) + } + + fun updatePendingPairingToSettled(proposalTopic: String, settledTopic: String, expirySeconds: Long, sequenceStatus: SequenceStatus) { + sessionDatabase.pairingDaoQueries.updatePendingPairingToSettled(settledTopic, expirySeconds, sequenceStatus, proposalTopic) + } + + fun deletePairing(topic: String) { + sessionDatabase.pairingDaoQueries.deletePairing(topic) + } + + fun insertSessionProposal(proposal: Session.Proposal, appMetaData: AppMetaData?, defaultExpirySeconds: Long, controllerType: ControllerType) { + val metadataId = insertMetaData(appMetaData) + + sessionDatabase.sessionDaoQueries.insertSession( + topic = proposal.topic.value, + permissions_chains = proposal.permissions.blockchain.chains, + permissions_methods = proposal.permissions.jsonRpc.methods, + permissions_types = proposal.permissions.notifications.types, + ttl_seconds = proposal.ttl.seconds, + expiry = defaultExpirySeconds, + status = SequenceStatus.PENDING, + controller_type = controllerType, + metadata_id = metadataId + ) + } + + private fun insertMetaData(appMetaData: AppMetaData?): Long { + return appMetaData?.let { + sessionDatabase.metaDataDaoQueries.insertOrIgnoreMetaData( + appMetaData.name, + appMetaData.description, + appMetaData.url, + appMetaData.icons + ) + + sessionDatabase.metaDataDaoQueries.lastInsertedRowId().executeAsOne() + } ?: FAILED_INSERT_ID + } + + fun updateStatusToSessionApproval( + topicKey: String, + subscriptionId: Long, + settledTopic: String, + accounts: List, + expirySeconds: Long + ) { + sessionDatabase.sessionDaoQueries.updateSessionWithSessionApproval( + subscriptionId, + settledTopic, + accounts, + expirySeconds, + SequenceStatus.SETTLED, + topicKey + ) + } + + fun updateSessionWithAccounts(topic: String, accounts: List) { + sessionDatabase.sessionDaoQueries.updateSessionWithAccounts(accounts, topic) + } + + fun updateSessionWithPermissions(topic: String, blockChains: List?, jsonRpcMethods: List?) { + val (listOfChains, listOfMethods) = sessionDatabase.sessionDaoQueries.getPermissionsByTopic(topic).executeAsOne() + val chainsUnion = listOfChains.union((blockChains ?: emptyList())).toList() + val methodsUnion = listOfMethods.union((jsonRpcMethods ?: emptyList())).toList() + sessionDatabase.sessionDaoQueries.updateSessionWithPermissions(chainsUnion, methodsUnion, topic) + } + + fun deleteSession(topic: String) { + sessionDatabase.metaDataDaoQueries.deleteMetaDataFromTopic(topic) + sessionDatabase.sessionDaoQueries.deleteSession(topic) + } + + private fun mapPairingDaoToPairingVO( + topic: String, + expirySeconds: Long, + uri: String, + status: SequenceStatus, + controller_type: ControllerType + ): PairingVO { + return PairingVO(Topic(topic), Expiry(expirySeconds), uri, status) + } + + private fun mapSessionDaoToSessionVO( + topic: String, + permission_chains: List, + permissions_methods: List, + permissions_types: List, + ttl_seconds: Long, + accounts: List?, + expiry: Long, + status: SequenceStatus, + controller_type: ControllerType, // TODO: Figure out how to handle proposer and responder once proposer is implemented + metadataName: String?, + metadataDesc: String?, + metadataUrl: String?, + metadataIcons: List? + ): SessionVO { + val appMetaData = if (metadataName != null && metadataDesc != null && metadataUrl != null && metadataIcons != null) { + AppMetaDataVO(metadataName, metadataDesc, metadataUrl, metadataIcons) + } else { + null + } + + return SessionVO( + topic = Topic(topic), + chains = permission_chains, + methods = permissions_methods, + types = permissions_types, + ttl = Ttl(ttl_seconds), + accounts = accounts ?: emptyList(), + expiry = Expiry(expiry), + status = status, + appMetaData = appMetaData + ) + } + + companion object { + private const val FAILED_INSERT_ID = -1L + internal val listOfStringsAdapter = object : ColumnAdapter, String> { + + override fun decode(databaseValue: String) = + if (databaseValue.isEmpty()) { + listOf() + } else { + databaseValue.split(",") + } + + override fun encode(value: List) = value.joinToString(separator = ",") + } + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/AppMetaDataVO.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/AppMetaDataVO.kt new file mode 100644 index 0000000000..d62a59fdf1 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/AppMetaDataVO.kt @@ -0,0 +1,8 @@ +package com.walletconnect.walletconnectv2.storage.data.vo + +data class AppMetaDataVO( + val name: String, + val description: String, + val url: String, + val icons: List +) diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/PairingVO.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/PairingVO.kt new file mode 100644 index 0000000000..246ee042a1 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/PairingVO.kt @@ -0,0 +1,12 @@ +package com.walletconnect.walletconnectv2.storage.data.vo + +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.storage.SequenceStatus + +data class PairingVO( + val topic: Topic, + val expiry: Expiry, + val uri: String, + val status: SequenceStatus +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/SessionVO.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/SessionVO.kt new file mode 100644 index 0000000000..dd7f0cf34e --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/storage/data/vo/SessionVO.kt @@ -0,0 +1,18 @@ +package com.walletconnect.walletconnectv2.storage.data.vo + +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.storage.SequenceStatus + +data class SessionVO( + val topic: Topic, + val chains: List, + val methods: List, + val types: List, + val ttl: Ttl, + val accounts: List, + val expiry: Expiry, + val status: SequenceStatus, + val appMetaData: AppMetaDataVO? +) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/Extensions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/Extensions.kt new file mode 100644 index 0000000000..516f9dcdaf --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/Extensions.kt @@ -0,0 +1,4 @@ +package com.walletconnect.walletconnectv2.util + +val String.Companion.Empty get() = "" +val Int.Companion.DefaultId get() = -1 \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/JsonExtensions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/JsonExtensions.kt similarity index 96% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/JsonExtensions.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/JsonExtensions.kt index 1145b26ac1..a4f2566459 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/JsonExtensions.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/JsonExtensions.kt @@ -1,6 +1,6 @@ @file:JvmName("JsonExtensions") -package org.walletconnect.walletconnectv2.util +package com.walletconnect.walletconnectv2.util import com.squareup.moshi.JsonReader import com.squareup.moshi.JsonWriter diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/Logger.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/Logger.kt new file mode 100644 index 0000000000..dbf4f7482e --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/Logger.kt @@ -0,0 +1,39 @@ +package com.walletconnect.walletconnectv2.util + +import com.walletconnect.walletconnectv2.BuildConfig +import timber.log.Timber + +object Logger { + const val TAG = "WalletConnectV2" + + init { + if (BuildConfig.DEBUG) { + // Source: https://medium.com/androiddevnotes/customize-your-android-timber-logger-setup-to-add-a-global-tag-and-a-method-name-to-the-logs-for-e7f23acd844f + Timber.plant( + object : Timber.DebugTree() { + /** + * Override [log] to modify the tag and add a "global tag" prefix to it. You can rename the String "global_tag_" as you see fit. + */ + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + super.log(priority, TAG, message, t) + } + }) + } + } + + fun log(logMsg: String?) { + Timber.d(logMsg) + } + + fun log(throwable: Throwable?) { + Timber.d(throwable) + } + + fun error(errorMsg: String?) { + Timber.e(errorMsg) + } + + fun error(throwable: Throwable?) { + Timber.e(throwable) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/UtilFunctions.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/UtilFunctions.kt new file mode 100644 index 0000000000..ceb9632192 --- /dev/null +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/UtilFunctions.kt @@ -0,0 +1,57 @@ +@file:JvmName("Utils") + +package com.walletconnect.walletconnectv2.util + +import java.lang.System.currentTimeMillis +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets + +fun generateId(): Long = (currentTimeMillis() + (100..999).random()) + +fun ByteArray.bytesToHex(): String { + val hexString = StringBuilder(2 * this.size) + for (i in this.indices) { + val hex = Integer.toHexString(0xff and this[i].toInt()) + if (hex.length == 1) { + hexString.append('0') + } + hexString.append(hex) + } + return hexString.toString() +} + +fun String.hexToBytes(): ByteArray { + val len = this.length + val data = ByteArray(len / 2) + var i = 0 + while (i < len) { + data[i / 2] = ((Character.digit(this[i], 16) shl 4) + + Character.digit(this[i + 1], 16)).toByte() + i += 2 + } + return data +} + +val String.hexToUtf8: String + get() { + var hex = this + hex = getHexPrefix(hex) + val buff = ByteBuffer.allocate(hex.length / 2) + var i = 0 + while (i < hex.length) { + buff.put(hex.substring(i, i + 2).toInt(16).toByte()) + i += 2 + } + buff.rewind() + val cb = StandardCharsets.UTF_8.decode(buff) + return cb.toString() + } + +private fun getHexPrefix(input: String): String = + if (containsHexPrefix(input)) { + input.substring(2) + } else { + input + } + +private fun containsHexPrefix(input: String): Boolean = input.startsWith("0x") \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/adapters/FlowStreamAdapter.kt b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/adapters/FlowStreamAdapter.kt similarity index 92% rename from walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/adapters/FlowStreamAdapter.kt rename to walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/adapters/FlowStreamAdapter.kt index 24ffca2a95..f4da59acf8 100644 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/adapters/FlowStreamAdapter.kt +++ b/walletconnectv2/src/main/kotlin/com/walletconnect/walletconnectv2/util/adapters/FlowStreamAdapter.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.util.adapters +package com.walletconnect.walletconnectv2.util.adapters import com.tinder.scarlet.Stream import com.tinder.scarlet.StreamAdapter diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/WalletConnectClient.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/WalletConnectClient.kt deleted file mode 100644 index 070df3c9fa..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/WalletConnectClient.kt +++ /dev/null @@ -1,73 +0,0 @@ -package org.walletconnect.walletconnectv2 - -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import org.walletconnect.walletconnectv2.client.ClientTypes -import org.walletconnect.walletconnectv2.client.SessionProposal -import org.walletconnect.walletconnectv2.client.WalletConnectClientListeners -import org.walletconnect.walletconnectv2.clientsync.session.Session -import org.walletconnect.walletconnectv2.engine.EngineInteractor -import timber.log.Timber -import java.net.URI - -object WalletConnectClient { - private val engineInteractor = EngineInteractor() - private var pairingListener: WalletConnectClientListeners.Pairing? = null - - init { - Timber.plant(Timber.DebugTree()) - - scope.launch { - engineInteractor.sessionProposal.collect { proposal -> - proposal?.toSessionProposal()?.let { sessionProposal -> - pairingListener?.onSessionProposal(sessionProposal) - } - } - } - } - - fun initialize(initialParams: ClientTypes.InitialParams) { - // TODO: pass properties to DI framework - val engineFactory = EngineInteractor.EngineFactory( - useTLs = initialParams.useTls, - hostName = initialParams.hostName, - apiKey = initialParams.apiKey, - isController = initialParams.isController, - application = initialParams.application, - metaData = initialParams.metadata - ) - engineInteractor.initialize(engineFactory) - } - - fun pair( - pairingParams: ClientTypes.PairParams, - clientListeners: WalletConnectClientListeners.Pairing - ) { - pairingListener = clientListeners - scope.launch { - engineInteractor.pair(pairingParams.uri) - } - } - - fun approve(approveParams: ClientTypes.ApproveParams) { - engineInteractor.approve(approveParams.accounts, approveParams.proposerPublicKey, approveParams.proposalTtl, approveParams.proposalTopic) - } - - fun reject(rejectParams: ClientTypes.RejectParams) { - engineInteractor.reject(rejectParams.rejectionReason, rejectParams.proposalTopic) - } - - private fun Session.Proposal.toSessionProposal(): SessionProposal { - return SessionProposal( - name = this.proposer.metadata?.name!!, - description = this.proposer.metadata.description, - dappUrl = this.proposer.metadata.url, - icon = this.proposer.metadata.icons.map { URI(it) }, - chains = this.permissions.blockchain.chains, - methods = this.permissions.jsonRpc.methods, - topic = this.topic.topicValue, - proposerPublicKey = this.proposer.publicKey, - ttl = this.ttl.seconds - ) - } -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/WalletConnectScope.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/WalletConnectScope.kt deleted file mode 100644 index 79924796a1..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/WalletConnectScope.kt +++ /dev/null @@ -1,16 +0,0 @@ -@file:JvmName("WalletConnectScope") - -package org.walletconnect.walletconnectv2 - -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import timber.log.Timber - -private val job = SupervisorJob() -internal val scope = CoroutineScope(job + Dispatchers.IO) - -internal val exceptionHandler = CoroutineExceptionHandler { _, exception -> - Timber.tag("WalletConnect exception").e(exception) -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/client/ClientListeners.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/client/ClientListeners.kt deleted file mode 100644 index e2f1aa7794..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/client/ClientListeners.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.walletconnect.walletconnectv2.client - -import java.net.URI - -sealed interface WalletConnectClientListeners { - - fun interface Pairing : WalletConnectClientListeners { - fun onSessionProposal(proposal: SessionProposal) - } -} - -data class SessionProposal( - val name: String, - val description: String, - val dappUrl: String, - val icon: List, - val chains: List, - var methods: List, - val topic: String, - val proposerPublicKey: String, - val ttl: Long, - val accounts: List = listOf() -) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/client/ClientTypes.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/client/ClientTypes.kt deleted file mode 100644 index 0b68ef3b04..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/client/ClientTypes.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.walletconnect.walletconnectv2.client - -import android.app.Application -import org.walletconnect.walletconnectv2.common.AppMetaData - -sealed class ClientTypes { - - data class InitialParams( - val application: Application, - val useTls: Boolean = true, - val hostName: String = WALLET_CONNECT_URL, - val apiKey: String = "", - val isController: Boolean = true, - val metadata: AppMetaData = AppMetaData() - ) : ClientTypes() - - data class PairParams(val uri: String) : ClientTypes() - - data class ApproveParams(val accounts: List, val proposerPublicKey: String, val proposalTtl: Long, val proposalTopic: String) - - data class RejectParams(val rejectionReason: String, val proposalTopic: String) - - companion object { - private const val WALLET_CONNECT_URL = "relay.walletconnect.com" - } -} diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PostSettlementPairing.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PostSettlementPairing.kt deleted file mode 100644 index a824d33d38..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/PostSettlementPairing.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync - -sealed class PostSettlementPairing \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/Pairing.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/Pairing.kt deleted file mode 100644 index 46fbe12ad6..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/Pairing.kt +++ /dev/null @@ -1,47 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.json.JSONObject -import org.walletconnect.walletconnectv2.common.Expiry -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.common.Ttl -import org.walletconnect.walletconnectv2.common.network.adapters.ExpiryAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.JSONObjectAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter -import org.walletconnect.walletconnectv2.clientsync.pairing.proposal.PairingProposedPermissions -import org.walletconnect.walletconnectv2.clientsync.pairing.proposal.PairingProposer -import org.walletconnect.walletconnectv2.clientsync.pairing.proposal.PairingSignal -import org.walletconnect.walletconnectv2.clientsync.pairing.success.PairingParticipant -import org.walletconnect.walletconnectv2.clientsync.pairing.success.PairingState - -sealed class Pairing { - - data class Proposal( - val topic: Topic, - val relay: JSONObject, - val pairingProposer: PairingProposer, - val pairingSignal: PairingSignal?, - val permissions: PairingProposedPermissions?, - val ttl: Ttl - ): Pairing() - - @JsonClass(generateAdapter = true) - data class Success( - @Json(name = "topic") - @TopicAdapter.Qualifier - val settledTopic: Topic, - @Json(name = "relay") - @JSONObjectAdapter.Qualifier - val relay: JSONObject, - @Json(name = "responder") - val responder: PairingParticipant, - @Json(name = "expiry") - @ExpiryAdapter.Qualifier - val expiry: Expiry, - @Json(name = "state") - val state: PairingState - ): Pairing() - - class Failure(val reason: String): Pairing() -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/PairingPayload.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/PairingPayload.kt deleted file mode 100644 index 48ef2b4271..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/PairingPayload.kt +++ /dev/null @@ -1,31 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.clientsync.session.Session - -@JsonClass(generateAdapter = true) -data class PairingPayload( - @Json(name = "id") - val id: Long, - @Json(name = "jsonrpc") - val jsonrpc: String, - @Json(name = "method") - val method: String, - @Json(name = "params") - val params: Params -) { - @JsonClass(generateAdapter = true) - data class Params( - @Json(name = "request") - val request: Request - ) { - @JsonClass(generateAdapter = true) - data class Request( - @Json(name = "method") - val method: String, - @Json(name = "params") - val params: Session.Proposal - ) - } -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/SettledPairingSequence.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/SettledPairingSequence.kt deleted file mode 100644 index 027c4b3707..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/SettledPairingSequence.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing - -import org.json.JSONObject -import org.walletconnect.walletconnectv2.clientsync.pairing.proposal.PairingProposedPermissions -import org.walletconnect.walletconnectv2.common.Expiry -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.crypto.data.PublicKey - -data class SettledPairingSequence( - val settledTopic: Topic, - val relay: JSONObject, - val selfPublicKey: PublicKey, - val peerPublicKey: PublicKey, - val sequencePermissions: Pair, - val expiry: Expiry -) diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/JsonRPC.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/JsonRPC.kt deleted file mode 100644 index 940f479d4b..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/JsonRPC.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing.proposal - - -data class JsonRPC(val methods: List) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingProposedPermissions.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingProposedPermissions.kt deleted file mode 100644 index 2aeea01089..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingProposedPermissions.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing.proposal - - -data class PairingProposedPermissions(val jsonRPC: JsonRPC) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingProposer.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingProposer.kt deleted file mode 100644 index d624484bf4..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingProposer.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing.proposal - -data class PairingProposer(val publicKey: String, val controller: Boolean) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingSignal.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingSignal.kt deleted file mode 100644 index e01c143ad3..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingSignal.kt +++ /dev/null @@ -1,3 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing.proposal - -data class PairingSignal(val type: String, val params: PairingSignalParams) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingSignalParams.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingSignalParams.kt deleted file mode 100644 index 64591540de..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/pairing/proposal/PairingSignalParams.kt +++ /dev/null @@ -1,4 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.pairing.proposal - - -data class PairingSignalParams(val uri: String) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/Session.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/Session.kt deleted file mode 100644 index 4f575c406b..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/Session.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.session - -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass -import org.walletconnect.walletconnectv2.clientsync.session.proposal.RelayProtocolOptions -import org.walletconnect.walletconnectv2.clientsync.session.proposal.SessionProposedPermissions -import org.walletconnect.walletconnectv2.clientsync.session.proposal.SessionProposer -import org.walletconnect.walletconnectv2.clientsync.session.proposal.SessionSignal -import org.walletconnect.walletconnectv2.clientsync.session.success.SessionParticipant -import org.walletconnect.walletconnectv2.clientsync.session.success.SessionState -import org.walletconnect.walletconnectv2.common.Expiry -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.common.Ttl -import org.walletconnect.walletconnectv2.common.network.adapters.ExpiryAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.TtlAdapter - -sealed class Session { - - @JsonClass(generateAdapter = true) - data class Proposal( - @Json(name = "topic") - @field:TopicAdapter.Qualifier - val topic: Topic, - @Json(name = "relay") - val relay: RelayProtocolOptions, - @Json(name = "proposer") - val proposer: SessionProposer, - @Json(name = "signal") - val signal: SessionSignal, - @Json(name = "permissions") - val permissions: SessionProposedPermissions, - @Json(name = "ttl") - @field:TtlAdapter.Qualifier - val ttl: Ttl - ) : Session() - - @JsonClass(generateAdapter = true) - data class Success( - @Json(name = "relay") - val relay: RelayProtocolOptions, - @Json(name = "responder") - val responder: SessionParticipant, - @Json(name = "expiry") - @ExpiryAdapter.Qualifier - val expiry: Expiry, - @Json(name = "state") - val state: SessionState - ) : Session() - - class Failure(val reason: String) : Session() -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/SettledSessionSequence.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/SettledSessionSequence.kt deleted file mode 100644 index 2da068c54d..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/SettledSessionSequence.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.session - -import org.walletconnect.walletconnectv2.clientsync.session.proposal.RelayProtocolOptions -import org.walletconnect.walletconnectv2.common.Expiry -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.crypto.data.PublicKey -import org.walletconnect.walletconnectv2.clientsync.session.success.SessionState - -data class SettledSessionSequence( - val settledTopic: Topic, - val relay: RelayProtocolOptions, - val selfPublicKey: PublicKey, - val peerPublicKey: PublicKey, - val sharedKey: String, - val expiry: Expiry, - val state: SessionState -) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/RelayProtocolOptions.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/RelayProtocolOptions.kt deleted file mode 100644 index 3b0d868271..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/clientsync/session/proposal/RelayProtocolOptions.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.walletconnect.walletconnectv2.clientsync.session.proposal - -data class RelayProtocolOptions( - val protocol: String = "waku" -) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/MappingFunctions.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/MappingFunctions.kt deleted file mode 100644 index ae9d197099..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/common/MappingFunctions.kt +++ /dev/null @@ -1,77 +0,0 @@ -@file:JvmName("MappingFunctions") - -package org.walletconnect.walletconnectv2.common - -import com.squareup.moshi.Moshi -import org.json.JSONObject -import org.walletconnect.walletconnectv2.clientsync.PreSettlementPairing -import org.walletconnect.walletconnectv2.clientsync.pairing.Pairing -import org.walletconnect.walletconnectv2.clientsync.pairing.proposal.PairingProposer -import org.walletconnect.walletconnectv2.clientsync.pairing.success.PairingParticipant -import org.walletconnect.walletconnectv2.clientsync.pairing.success.PairingState -import org.walletconnect.walletconnectv2.crypto.data.PublicKey -import org.walletconnect.walletconnectv2.relay.data.model.Relay -import java.net.URI -import kotlin.time.Duration - -internal fun String.toPairProposal(): Pairing.Proposal { - val properUriString = if (contains("wc://")) this else replace("wc:", "wc://") - val pairUri = URI(properUriString) - val mapOfQueryParameters: Map = - pairUri.query.split("&").associate { it.substringBefore("=") to it.substringAfter("=") } - val relay = JSONObject(mapOfQueryParameters["relay"] ?: "{}") - val publicKey = mapOfQueryParameters["publicKey"] ?: "" - val controller: Boolean = mapOfQueryParameters["controller"].toBoolean() - val ttl: Long = Duration.days(30).inWholeSeconds - - return Pairing.Proposal( - topic = Topic(pairUri.userInfo), - relay = relay, - pairingProposer = PairingProposer(publicKey, controller), - pairingSignal = null, - permissions = null, - ttl = Ttl(ttl) - ) -} - -internal fun Pairing.Proposal.toPairingSuccess( - settleTopic: Topic, - expiry: Expiry, - selfPublicKey: PublicKey -): Pairing.Success { - return Pairing.Success( - settledTopic = settleTopic, - relay = relay, - responder = PairingParticipant(publicKey = selfPublicKey.keyAsHex), - expiry = expiry, - state = PairingState(null) - ) -} - -internal fun Pairing.Proposal.toApprove( - id: Long, - settleTopic: Topic, - expiry: Expiry, - selfPublicKey: PublicKey -): PreSettlementPairing.Approve { - return PreSettlementPairing.Approve( - id = id, - params = this.toPairingSuccess(settleTopic, expiry, selfPublicKey) - ) -} - -internal fun PreSettlementPairing.Approve.toRelayPublishRequest( - id: Long, - topic: Topic, - moshi: Moshi -): Relay.Publish.Request { - val pairingApproveJson = moshi.adapter(PreSettlementPairing.Approve::class.java).toJson(this) - val hexEncodedJson = pairingApproveJson.encodeToByteArray().joinToString(separator = "") { - String.format("%02X", it) - } - - return Relay.Publish.Request( - id = id, - params = Relay.Publish.Request.Params(topic = topic, message = hexEncodedJson.lowercase()) - ) -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/Codec.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/Codec.kt deleted file mode 100644 index 774af994d1..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/Codec.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.walletconnect.walletconnectv2.crypto - -import org.walletconnect.walletconnectv2.crypto.data.EncryptionPayload -import org.walletconnect.walletconnectv2.crypto.data.PublicKey - -interface Codec { - fun encrypt(message: String, sharedKey: String, publicKey: PublicKey): EncryptionPayload - - fun decrypt(payload: EncryptionPayload, sharedKey: String): String -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/CryptoManager.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/CryptoManager.kt deleted file mode 100644 index a4d792fd35..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/CryptoManager.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.walletconnect.walletconnectv2.crypto - -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.crypto.data.PublicKey - -interface CryptoManager { - - fun hasKeys(tag: String): Boolean - - fun generateKeyPair(): PublicKey - - fun generateTopicAndSharedKey(self: PublicKey, peer: PublicKey, overrideTopic: String? = null): Pair - - fun getSharedKey(self: PublicKey, peer: PublicKey): String -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/KeyChain.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/KeyChain.kt deleted file mode 100644 index 1e5c284331..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/KeyChain.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.walletconnect.walletconnectv2.crypto - -interface KeyChain { - - fun setKey(key: String, value: String) - - fun getKey(key: String): String -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/data/Keys.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/data/Keys.kt deleted file mode 100644 index 86b4b9abe9..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/data/Keys.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.walletconnect.walletconnectv2.crypto.data - -interface Key { - val keyAsHex: String -} - -@JvmInline -value class PublicKey(override val keyAsHex: String): Key - -@JvmInline -value class PrivateKey(override val keyAsHex: String): Key \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/managers/LazySodiumCryptoManager.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/managers/LazySodiumCryptoManager.kt deleted file mode 100644 index 7da50bb4c9..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/crypto/managers/LazySodiumCryptoManager.kt +++ /dev/null @@ -1,112 +0,0 @@ -package org.walletconnect.walletconnectv2.crypto.managers - -import com.goterl.lazysodium.LazySodiumAndroid -import com.goterl.lazysodium.SodiumAndroid -import com.goterl.lazysodium.utils.HexMessageEncoder -import com.goterl.lazysodium.utils.Key -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.crypto.CryptoManager -import org.walletconnect.walletconnectv2.crypto.KeyChain -import org.walletconnect.walletconnectv2.crypto.data.PrivateKey -import org.walletconnect.walletconnectv2.crypto.data.PublicKey -import org.walletconnect.walletconnectv2.util.bytesToHex -import org.walletconnect.walletconnectv2.util.hexToBytes -import java.security.MessageDigest -import org.walletconnect.walletconnectv2.crypto.data.Key as WCKey - -class LazySodiumCryptoManager(private val keyChain: KeyChain) : CryptoManager { - private val lazySodium = LazySodiumAndroid(SodiumAndroid()) - - override fun hasKeys(tag: String): Boolean { - return keyChain.getKey(tag).isNotBlank() - } - - override fun generateKeyPair(): PublicKey { - val lsKeyPair = lazySodium.cryptoSignKeypair() - val curve25519KeyPair = lazySodium.convertKeyPairEd25519ToCurve25519(lsKeyPair) - - val (publicKey, privateKey) = curve25519KeyPair.let { keyPair -> - PublicKey(keyPair.publicKey.asHexString.lowercase()) to PrivateKey(keyPair.secretKey.asHexString.lowercase()) - } - - setKeyPair(publicKey, privateKey) - return publicKey - } - - override fun generateTopicAndSharedKey( - self: PublicKey, - peer: PublicKey, - overrideTopic: String? - ): Pair { - val (publicKey, privateKey) = getKeyPair(self) - val sharedKey = lazySodium.cryptoScalarMult(privateKey.toKey(), peer.toKey()) - - return Pair( - sharedKey.asHexString.lowercase(), - setEncryptionKeys(sharedKey.asHexString.lowercase(), publicKey, overrideTopic) - ) - } - - override fun getSharedKey(self: PublicKey, peer: PublicKey): String { - val (_, selfPrivateKey) = getKeyPair(self) - return lazySodium.cryptoScalarMult(selfPrivateKey.toKey(), peer.toKey()).asHexString - } - - internal fun getSharedKeyUsingPrivate(self: PrivateKey, peer: PublicKey): String { - return lazySodium.cryptoScalarMult(self.toKey(), peer.toKey()).asHexString - } - - internal fun setEncryptionKeys( - sharedKey: String, - selfPublicKey: PublicKey, - overrideTopic: String? - ): Topic { - val messageDigest: MessageDigest = MessageDigest.getInstance("SHA-256") - val hashedBytes: ByteArray = messageDigest.digest(sharedKey.hexToBytes()) - val topic = Topic(hashedBytes.bytesToHex()) - - val sharedKeyObject = object : WCKey { - override val keyAsHex: String = sharedKey - } - val keys = concatKeys(sharedKeyObject, selfPublicKey) - - keyChain.setKey(topic.topicValue, keys) - return topic - } - - internal fun setKeyPair(publicKey: PublicKey, privateKey: PrivateKey) { - val keys = concatKeys(publicKey, privateKey) - - keyChain.setKey(publicKey.keyAsHex, keys) - } - - internal fun getKeyPair(wcKey: WCKey): Pair { - val storageKey: String = keyChain.getKey(wcKey.keyAsHex) - - return splitKeys(storageKey) - } - - internal fun concatKeys(keyA: WCKey, keyB: WCKey): String { - val encoder = HexMessageEncoder() - return encoder.encode(encoder.decode(keyA.keyAsHex) + encoder.decode(keyB.keyAsHex)) - } - - internal fun splitKeys(concatKeys: String): Pair { - val hexEncoder = HexMessageEncoder() - val concatKeysByteArray = hexEncoder.decode(concatKeys) - val privateKeyByteArray = - concatKeysByteArray.sliceArray(0 until (concatKeysByteArray.size / 2)) - val publicKeyByteArray = - concatKeysByteArray.sliceArray((concatKeysByteArray.size / 2) until concatKeysByteArray.size) - - return PublicKey(hexEncoder.encode(privateKeyByteArray)) to PrivateKey( - hexEncoder.encode( - publicKeyByteArray - ) - ) - } - - private fun WCKey.toKey(): Key { - return Key.fromHexString(keyAsHex) - } -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/engine/EngineInteractor.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/engine/EngineInteractor.kt deleted file mode 100644 index e17b10a7b6..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/engine/EngineInteractor.kt +++ /dev/null @@ -1,242 +0,0 @@ -package org.walletconnect.walletconnectv2.engine - -import android.app.Application -import com.tinder.scarlet.WebSocket -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.launch -import kotlinx.coroutines.supervisorScope -import org.json.JSONObject -import org.walletconnect.walletconnectv2.client.SessionProposal -import org.walletconnect.walletconnectv2.clientsync.PreSettlementSession -import org.walletconnect.walletconnectv2.clientsync.pairing.SettledPairingSequence -import org.walletconnect.walletconnectv2.clientsync.pairing.proposal.PairingProposedPermissions -import org.walletconnect.walletconnectv2.clientsync.session.Session -import org.walletconnect.walletconnectv2.clientsync.session.SettledSessionSequence -import org.walletconnect.walletconnectv2.clientsync.session.proposal.RelayProtocolOptions -import org.walletconnect.walletconnectv2.clientsync.session.success.SessionParticipant -import org.walletconnect.walletconnectv2.clientsync.session.success.SessionState -import org.walletconnect.walletconnectv2.common.* -import org.walletconnect.walletconnectv2.crypto.CryptoManager -import org.walletconnect.walletconnectv2.crypto.KeyChain -import org.walletconnect.walletconnectv2.crypto.codec.AuthenticatedEncryptionCodec -import org.walletconnect.walletconnectv2.crypto.data.EncryptionPayload -import org.walletconnect.walletconnectv2.crypto.data.PublicKey -import org.walletconnect.walletconnectv2.crypto.managers.LazySodiumCryptoManager -import org.walletconnect.walletconnectv2.errors.exception -import org.walletconnect.walletconnectv2.exceptionHandler -import org.walletconnect.walletconnectv2.relay.WakuRelayRepository -import org.walletconnect.walletconnectv2.scope -import org.walletconnect.walletconnectv2.util.generateId -import org.walletconnect.walletconnectv2.util.toEncryptionPayload -import java.util.* - -class EngineInteractor { - //region provide with DI - // TODO: add logic to check hostName for ws/wss scheme with and without :// - private lateinit var relayRepository: WakuRelayRepository - private val keyChain = object : KeyChain { - val mapOfKeys = mutableMapOf() - - override fun setKey(key: String, value: String) { - mapOfKeys[key] = value - } - - override fun getKey(key: String): String { - return mapOfKeys[key]!! - } - } - private val crypto: CryptoManager = LazySodiumCryptoManager(keyChain) - private val codec: AuthenticatedEncryptionCodec = AuthenticatedEncryptionCodec() - - //endregion - private var metaData: AppMetaData? = null - private val _sessionProposal: MutableStateFlow = MutableStateFlow(null) - val sessionProposal: StateFlow = _sessionProposal - - //todo create topic -> keys map - private var pairingPublicKey = PublicKey("") - private var peerPublicKey = PublicKey("") - private var pairingSharedKey: String = "" - - fun initialize(engineFactory: EngineFactory) { - this.metaData = engineFactory.metaData - relayRepository = WakuRelayRepository.initRemote( - engineFactory.useTLs, - engineFactory.hostName, - engineFactory.apiKey, - engineFactory.application - ) - - scope.launch(exceptionHandler) { - relayRepository.eventsFlow - .filterIsInstance() - .collect { event -> - throw event.throwable.exception - } - } - - scope.launch { - relayRepository.subscriptionRequest.collect { request -> - supervisorScope { - relayRepository.publishSessionProposalAcknowledgment(request.id) - } - - val pairingPayloadJson = codec.decrypt( - request.params.subscriptionData.message.toEncryptionPayload(), - crypto.getSharedKey(pairingPublicKey, peerPublicKey) - ) - val pairingPayload = relayRepository.parseToPairingPayload(pairingPayloadJson) - val sessionProposal = pairingPayload?.params?.request?.params - _sessionProposal.value = sessionProposal - } - } - } - - fun pair(uri: String) { - require(::relayRepository.isInitialized) - val pairingProposal = uri.toPairProposal() - val selfPublicKey = crypto.generateKeyPair().also { pairingPublicKey = it } - val expiry = - Expiry((Calendar.getInstance().timeInMillis / 1000) + pairingProposal.ttl.seconds) - val peerPublicKey = - PublicKey(pairingProposal.pairingProposer.publicKey).also { peerPublicKey = it } - val controllerPublicKey = if (pairingProposal.pairingProposer.controller) { - peerPublicKey - } else { - selfPublicKey - } - - val settledSequence = settlePairingSequence( - pairingProposal.relay, - selfPublicKey, - peerPublicKey, - pairingProposal.permissions, - controllerPublicKey, - expiry - ) - val preSettlementPairingApprove = - pairingProposal.toApprove( - generateId(), - settledSequence.settledTopic, - expiry, - selfPublicKey - ) - - relayRepository.subscribe(settledSequence.settledTopic) - relayRepository.publishPairingApproval(pairingProposal.topic, preSettlementPairingApprove) - } - - fun approve(accounts: List, proposerPublicKey: String, proposalTtl: Long, proposalTopic: String) { - require(::relayRepository.isInitialized) - val selfPublicKey: PublicKey = crypto.generateKeyPair() - val peerPublicKey = PublicKey(proposerPublicKey) - val sessionState = SessionState(accounts) - val expiry = Expiry((Calendar.getInstance().timeInMillis / 1000) + proposalTtl) - - val settledSession: SettledSessionSequence = settleSessionSequence( - RelayProtocolOptions(), - selfPublicKey, - peerPublicKey, - expiry, - sessionState - ) - - val preSettlementSession = PreSettlementSession.Approve( - id = generateId(), - params = Session.Success( - relay = RelayProtocolOptions(), - state = settledSession.state, - expiry = expiry, - responder = SessionParticipant( - selfPublicKey.keyAsHex, - metadata = this.metaData - ) - ) - ) - - val sessionApprovalJson: String = - relayRepository.getSessionApprovalJson(preSettlementSession) - - val encryptedJson: EncryptionPayload = codec.encrypt( - sessionApprovalJson, - pairingSharedKey, - pairingPublicKey - ) - - val encryptedString = encryptedJson.iv + encryptedJson.publicKey + encryptedJson.mac + encryptedJson.cipherText - - relayRepository.publish(Topic(proposalTopic), encryptedString) - } - - - fun reject(reason: String, proposalTopic: String) { - val preSettlementSession = - PreSettlementSession.Reject(id = generateId(), params = Session.Failure(reason)) - val sessionRejectionJson: String = - relayRepository.getSessionRejectionJson(preSettlementSession) - - val encryptedJson: EncryptionPayload = codec.encrypt( - sessionRejectionJson, - pairingSharedKey, - pairingPublicKey - ) - val encryptedString = - encryptedJson.iv + encryptedJson.publicKey + encryptedJson.mac + encryptedJson.cipherText - - relayRepository.publish(Topic(proposalTopic), encryptedString) - } - - private fun settlePairingSequence( - relay: JSONObject, - selfPublicKey: PublicKey, - peerPublicKey: PublicKey, - permissions: PairingProposedPermissions?, - controllerPublicKey: PublicKey, - expiry: Expiry - ): SettledPairingSequence { - require(::relayRepository.isInitialized) - val (sharedKey, settledTopic) = - crypto.generateTopicAndSharedKey(selfPublicKey, peerPublicKey) - pairingSharedKey = sharedKey - return SettledPairingSequence( - settledTopic, - relay, - selfPublicKey, - peerPublicKey, - permissions to controllerPublicKey, - expiry - ) - } - - private fun settleSessionSequence( - relay: RelayProtocolOptions, - selfPublicKey: PublicKey, - peerPublicKey: PublicKey, - expiry: Expiry, - sessionState: SessionState - ): SettledSessionSequence { - val (sharedKey, settledTopic) = - crypto.generateTopicAndSharedKey(selfPublicKey, peerPublicKey) - return SettledSessionSequence( - settledTopic, - relay, - selfPublicKey, - peerPublicKey, - sharedKey, - expiry, - sessionState - ) - } - - data class EngineFactory( - val useTLs: Boolean = false, - val hostName: String, - val apiKey: String, - val isController: Boolean, - val application: Application, - val metaData: AppMetaData - ) -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/errors/Exceptions.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/errors/Exceptions.kt deleted file mode 100644 index bd8202cfc0..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/errors/Exceptions.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.walletconnect.walletconnectv2.errors - -class ApiKeyDoesNotExistException(override val message: String?) : Exception(message) -class InvalidApiKeyException(override val message: String?) : Exception(message) -class ServerException(override val message: String?) : Exception(message) \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/WakuRelayRepository.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/WakuRelayRepository.kt deleted file mode 100644 index a70914b8da..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/relay/WakuRelayRepository.kt +++ /dev/null @@ -1,131 +0,0 @@ -package org.walletconnect.walletconnectv2.relay - -import android.app.Application -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import com.tinder.scarlet.Scarlet -import com.tinder.scarlet.lifecycle.android.AndroidLifecycle -import com.tinder.scarlet.messageadapter.moshi.MoshiMessageAdapter -import com.tinder.scarlet.retry.LinearBackoffStrategy -import com.tinder.scarlet.utils.getRawType -import com.tinder.scarlet.websocket.okhttp.newWebSocketFactory -import okhttp3.OkHttpClient -import org.json.JSONObject -import org.walletconnect.walletconnectv2.clientsync.PreSettlementPairing -import org.walletconnect.walletconnectv2.clientsync.PreSettlementSession -import org.walletconnect.walletconnectv2.clientsync.pairing.PairingPayload -import org.walletconnect.walletconnectv2.common.* -import org.walletconnect.walletconnectv2.common.network.adapters.* -import org.walletconnect.walletconnectv2.relay.data.RelayService -import org.walletconnect.walletconnectv2.relay.data.model.Relay -import org.walletconnect.walletconnectv2.util.adapters.FlowStreamAdapter -import org.walletconnect.walletconnectv2.util.generateId -import java.util.concurrent.TimeUnit - -class WakuRelayRepository internal constructor( - private val useTLs: Boolean, - private val hostName: String, - private val apiKey: String, - private val application: Application -) { - //region Move to DI module - private val okHttpClient = OkHttpClient.Builder() - .writeTimeout(TIMEOUT_TIME, TimeUnit.MILLISECONDS) - .readTimeout(TIMEOUT_TIME, TimeUnit.MILLISECONDS) - .callTimeout(TIMEOUT_TIME, TimeUnit.MILLISECONDS) - .connectTimeout(TIMEOUT_TIME, TimeUnit.MILLISECONDS) - .pingInterval(5, TimeUnit.SECONDS) - .build() - - private val moshi: Moshi = Moshi.Builder() - .addLast { type, _, _ -> - when (type.getRawType().name) { - Expiry::class.qualifiedName -> ExpiryAdapter - JSONObject::class.qualifiedName -> JSONObjectAdapter - SubscriptionId::class.qualifiedName -> SubscriptionIdAdapter - Topic::class.qualifiedName -> TopicAdapter - Ttl::class.qualifiedName -> TtlAdapter - else -> null - } - } - .addLast(KotlinJsonAdapterFactory()) - .build() - private val scarlet by lazy { - Scarlet.Builder() - .backoffStrategy(LinearBackoffStrategy(TimeUnit.MINUTES.toMillis(DEFAULT_BACKOFF_MINUTES))) - .webSocketFactory(okHttpClient.newWebSocketFactory(getServerUrl())) - .lifecycle(AndroidLifecycle.ofApplicationForeground(application)) - .addMessageAdapterFactory(MoshiMessageAdapter.Factory(moshi)) - .addStreamAdapterFactory(FlowStreamAdapter.Factory()) - .build() - } - private val relay: RelayService by lazy { scarlet.create(RelayService::class.java) } - //endregion - - internal val eventsFlow = relay.eventsFlow() - internal val publishAcknowledgement = relay.observePublishAcknowledgement() - internal val subscribeAcknowledgement = relay.observeSubscribeAcknowledgement() - internal val subscriptionRequest = relay.observeSubscriptionRequest() - internal val unsubscribeAcknowledgement = relay.observeUnsubscribeAcknowledgement() - - fun publishPairingApproval( - topic: Topic, - preSettlementPairingApproval: PreSettlementPairing.Approve - ) { - val publishRequest = - preSettlementPairingApproval.toRelayPublishRequest(generateId(), topic, moshi) - relay.publishRequest(publishRequest) - } - - fun publish(topic: Topic, encryptedJson: String) { - val publishRequest = - Relay.Publish.Request( - id = generateId(), - params = Relay.Publish.Request.Params(topic = topic, message = encryptedJson) - ) - relay.publishRequest(publishRequest) - } - - fun publishSessionProposalAcknowledgment(id: Long) { - val publishRequest = - Relay.Subscription.Acknowledgement( - id = id, - result = true - ) - relay.publishSubscriptionAcknowledgment(publishRequest) - } - - fun subscribe(topic: Topic) { - val subscribeRequest = - Relay.Subscribe.Request( - id = generateId(), - params = Relay.Subscribe.Request.Params(topic) - ) - relay.subscribeRequest(subscribeRequest) - } - - fun getSessionApprovalJson(preSettlementSessionApproval: PreSettlementSession.Approve): String = - moshi.adapter(PreSettlementSession.Approve::class.java).toJson(preSettlementSessionApproval) - - fun getSessionRejectionJson(preSettlementSessionRejection: PreSettlementSession.Reject): String = - moshi.adapter(PreSettlementSession.Reject::class.java).toJson(preSettlementSessionRejection) - - fun parseToPairingPayload(json: String): PairingPayload? = - moshi.adapter(PairingPayload::class.java).fromJson(json) - - private fun getServerUrl(): String { - return ((if (useTLs) "wss" else "ws") + "://$hostName/?apiKey=$apiKey").trim() - } - - companion object { - private const val TIMEOUT_TIME = 5000L - private const val DEFAULT_BACKOFF_MINUTES = 5L - - fun initRemote( - useTLs: Boolean = false, - hostName: String, - apiKey: String, - application: Application - ) = WakuRelayRepository(useTLs, hostName, apiKey, application) - } -} \ No newline at end of file diff --git a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/UtilFunctions.kt b/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/UtilFunctions.kt deleted file mode 100644 index d57ac5186e..0000000000 --- a/walletconnectv2/src/main/kotlin/org/walletconnect/walletconnectv2/util/UtilFunctions.kt +++ /dev/null @@ -1,45 +0,0 @@ -@file:JvmName("Utils") - -package org.walletconnect.walletconnectv2.util - -import org.walletconnect.walletconnectv2.crypto.data.EncryptionPayload -import java.lang.System.currentTimeMillis - -fun generateId(): Long = (currentTimeMillis() + (0..100).random()) - -fun ByteArray.bytesToHex(): String { - val hexString = StringBuilder(2 * this.size) - for (i in this.indices) { - val hex = Integer.toHexString(0xff and this[i].toInt()) - if (hex.length == 1) { - hexString.append('0') - } - hexString.append(hex) - } - return hexString.toString() -} - -fun String.hexToBytes(): ByteArray { - val len = this.length - val data = ByteArray(len / 2) - var i = 0 - while (i < len) { - data[i / 2] = ((Character.digit(this[i], 16) shl 4) - + Character.digit(this[i + 1], 16)).toByte() - i += 2 - } - return data -} - -fun String.toEncryptionPayload(): EncryptionPayload { - val pubKeyStartIndex = EncryptionPayload.ivLength - val macStartIndex = pubKeyStartIndex + EncryptionPayload.publicKeyLength - val cipherTextStartIndex = macStartIndex + EncryptionPayload.macLength - - val iv = this.substring(0, pubKeyStartIndex) - val publicKey = this.substring(pubKeyStartIndex, macStartIndex) - val mac = this.substring(macStartIndex, cipherTextStartIndex) - val cipherText = this.substring(cipherTextStartIndex, this.length) - - return EncryptionPayload(iv, publicKey, mac, cipherText) -} \ No newline at end of file diff --git a/walletconnectv2/src/main/res/xml/backup_rules.xml b/walletconnectv2/src/main/res/xml/backup_rules.xml new file mode 100644 index 0000000000..8ccd13e866 --- /dev/null +++ b/walletconnectv2/src/main/res/xml/backup_rules.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/MetaDataDao.sq b/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/MetaDataDao.sq new file mode 100644 index 0000000000..87f24ae1cc --- /dev/null +++ b/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/MetaDataDao.sq @@ -0,0 +1,30 @@ +import kotlin.collections.List; + +CREATE TABLE MetaDataDao( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + _name TEXT NOT NULL, + description TEXT NOT NULL, + url TEXT NOT NULL, + icons TEXT AS List NOT NULL +); + +-- TODO: change INSERT OR REPLACE and add logic to retrieve id instead +insertOrIgnoreMetaData: +INSERT OR IGNORE INTO MetaDataDao(_name, description, url, icons) +VALUES (?, ?, ?, ?); + +lastInsertedRowId: +SELECT last_insert_rowid(); + +getMetaData: +SELECT id, _name, description, url, icons +FROM MetaDataDao +LIMIT 1; + +deleteMetaDataFromTopic: +DELETE FROM MetaDataDao +WHERE id = ( + SELECT metadata_id + FROM SessionDao + WHERE topic = ? +); \ No newline at end of file diff --git a/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/PairingDao.sq b/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/PairingDao.sq new file mode 100644 index 0000000000..7144604d1d --- /dev/null +++ b/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/PairingDao.sq @@ -0,0 +1,28 @@ +import com.walletconnect.walletconnectv2.common.ControllerType; +import com.walletconnect.walletconnectv2.storage.SequenceStatus; + +CREATE TABLE PairingDao ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + topic TEXT UNIQUE NOT NULL, + uri TEXT NOT NULL, + expiry INTEGER NOT NULL, + status TEXT AS SequenceStatus NOT NULL, + controller_type TEXT AS ControllerType NOT NULL +); + +insertPairing: +INSERT OR IGNORE INTO PairingDao(topic, uri, expiry, status, controller_type) +VALUES (?, ?, ?, ?, ?); + +updatePendingPairingToSettled: +UPDATE PairingDao +SET topic = ?, expiry = ?, status = ? +WHERE topic = ?; + +getListOfPairingDaos: +SELECT topic, expiry, uri, status, controller_type +FROM PairingDao; + +deletePairing: +DELETE FROM PairingDao +WHERE ? = topic; \ No newline at end of file diff --git a/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/SessionDao.sq b/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/SessionDao.sq new file mode 100644 index 0000000000..ecc071e0ee --- /dev/null +++ b/walletconnectv2/src/main/sqldelight/org/walletconnect/walletconnectv2/storage/data/dao/SessionDao.sq @@ -0,0 +1,51 @@ +import kotlin.collections.List; +import com.walletconnect.walletconnectv2.storage.SequenceStatus; +import com.walletconnect.walletconnectv2.common.ControllerType; + +CREATE TABLE SessionDao( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + topic TEXT UNIQUE NOT NULL, + permissions_chains TEXT AS List NOT NULL, + permissions_methods TEXT AS List NOT NULL, + permissions_types TEXT AS List NOT NULL, + ttl_seconds INTEGER NOT NULL, + accounts TEXT AS List DEFAULT(NULL), + expiry INTEGER NOT NULL, + status TEXT AS SequenceStatus NOT NULL, + controller_type TEXT AS ControllerType NOT NULL, + metadata_id INTEGER +); + +insertSession: +INSERT OR IGNORE INTO SessionDao(topic, permissions_chains, permissions_methods, permissions_types, ttl_seconds, expiry, status, controller_type, metadata_id) +VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); + +getListOfSessionDaos: +SELECT sd.topic, sd.permissions_chains, sd.permissions_methods, sd.permissions_types, sd.ttl_seconds, sd.accounts, sd.expiry, sd.status, sd.controller_type, mdd._name, mdd.description, mdd.url, mdd.icons +FROM SessionDao sd + LEFT JOIN MetaDataDao mdd ON sd.metadata_id = mdd.id; + +updateSessionWithSessionApproval: +UPDATE OR ABORT SessionDao +SET id = ?, topic = ?, accounts = ?, expiry = ?, status = ? +WHERE topic = ?; + +updateSessionWithPermissions: +UPDATE OR ABORT SessionDao +SET permissions_chains = ?, permissions_methods = ? +WHERE topic = ?; + +updateSessionWithAccounts: +UPDATE OR ABORT SessionDao +SET accounts = ? +WHERE topic = ?; + +deleteSession: +DELETE FROM SessionDao +WHERE topic = ?; + +getPermissionsByTopic: +SELECT sd.permissions_chains, sd.permissions_methods +FROM SessionDao sd + LEFT JOIN MetaDataDao mdd ON sd.metadata_id = mdd.id +WHERE topic = ?; \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/GenericPayloadsTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/GenericPayloadsTest.kt new file mode 100644 index 0000000000..f875f7cda9 --- /dev/null +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/GenericPayloadsTest.kt @@ -0,0 +1,103 @@ +package com.walletconnect.walletconnectv2 + +import com.squareup.moshi.* +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import org.intellij.lang.annotations.Language +import org.junit.jupiter.api.Test +import kotlin.test.assertNotNull +import kotlin.test.assertNull + +class GenericPayloadsTest { + private val moshi: Moshi = Moshi.Builder() +// .add(SingleToArrayAdapter.INSTANCE) + .addLast(KotlinJsonAdapterFactory()) + .build() + + @Test + fun decodeObject() { + @Language("JSON") + val jsonString = """ + { + "boolKey": true, + "intKey": 1337, + "doubleKey": 13.37, + "stringKey": "verystringwow", + "objectKey": { + "subKey": "0xdeadbeef" + } + } + """.trimIndent() + + + @Language("JSON") + val jsonString2 = """ + ["0xdeadbeaf","0x9b2055d370f73ec7d8a03e965129118dc8f5bf83"] + """.trimIndent() + + val successfulDecode = decode(jsonString) +// println(decode(jsonString2)) // TODO: For generic adapter, Deserialize into JSONObject/JSONArray and then parse object and generate passed type + + assertNotNull(successfulDecode) + } + + @Test + fun decodeInvalidObject() { + @Language("JSON") + val invalidJsonString = """ + { + "boolKey": "invalid test", + "intKey": 1337, + "doubleKey": 13.37, + "stringKey": "verystringwow" + } + """.trimIndent() + + val failedDecode = decode(invalidJsonString) + + assertNull(failedDecode) + } + + private inline fun decode(jsonString: String): T? { + return try { + moshi.adapter(T::class.java).fromJson(jsonString) + } catch (exception: JsonDataException) { + null + } + } + + // Source: https://stackoverflow.com/questions/53344033/moshi-parse-single-object-or-list-of-objects-kotlin +// class SingleToArrayAdapter(val delegateAdapter: JsonAdapter>, val elementAdapter: JsonAdapter) : JsonAdapter() { +// +// companion object { +// val INSTANCE = SingleToArrayAdapterFactory() +// } +// +// override fun fromJson(reader: JsonReader): Any? = +// if (reader.peek() != JsonReader.Token.BEGIN_ARRAY) { +// Collections.singletonList(elementAdapter.fromJson(reader)) +// } else delegateAdapter.fromJson(reader) +// +// override fun toJson(writer: JsonWriter, value: Any?) = +// throw UnsupportedOperationException("SingleToArrayAdapter is only used to deserialize objects") +// +// class SingleToArrayAdapterFactory : JsonAdapter.Factory { +// override fun create(type: Type, annotations: Set, moshi: Moshi): JsonAdapter? { +// val elementType = Types.collectionElementType(type, List::class.java) +// val delegateAdapter: JsonAdapter> = moshi.adapter(type) +// val elementAdapter: JsonAdapter = moshi.adapter(elementType) +// +// return SingleToArrayAdapter(delegateAdapter, elementAdapter) +// } +// } +// } + + private data class SampleStruct( + val boolKey: Boolean, + val intKey: Int, + val doubleKey: Double, + val stringKey: String, + val objectKey: SubObject + ) { + data class SubObject(val subKey: String) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/MappingFunctionsTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/MappingFunctionsTest.kt new file mode 100644 index 0000000000..c2cc4729c7 --- /dev/null +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/MappingFunctionsTest.kt @@ -0,0 +1,67 @@ +package com.walletconnect.walletconnectv2.common + +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import com.walletconnect.walletconnectv2.clientsync.pairing.Pairing +import com.walletconnect.walletconnectv2.clientsync.pairing.before.proposal.PairingProposer +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingState +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.util.getRandom64ByteHexString +import kotlin.test.assertEquals + +internal class MappingFunctionsTest { + + @Test + fun `A proper URI mapped to a PairingProposal`() { + val testUri = + "wc:0b1a3d6c0336662dddb6278ee0aa25380569b79e7e86cfe39fb20b4b189096a0@2?controller=false&publicKey=66db1bd5fad65392d1d5a4856d0d549d2fca9194327138b41c289b961d147860&relay=%7B%22protocol%22%3A%22waku%22%7D" + + val pairingProposal = testUri.toPairProposal() + + assertNotNull(pairingProposal) + assert(pairingProposal.topic.value.isNotBlank()) + assert(pairingProposal.pairingProposer.publicKey.isNotBlank()) + assert(pairingProposal.ttl.seconds > 0) + } + + @Test + fun `PairingProposal mapped to PairingSuccess`() { + val pairingProposal = mockk { + every { topic } returns Topic("0x111") + every { relay } returns mockk() + every { pairingProposer } returns PairingProposer("0x123", false) + every { ttl } returns Ttl(2L) + } + + val pairingSuccess = pairingProposal.toPairingSuccess( + Topic("0x111"), + Expiry(10L), + PublicKey("0x123") + ) + + assertEquals(pairingProposal.topic, pairingSuccess.settledTopic) + assertEquals(pairingProposal.relay, pairingSuccess.relay) + assertEquals(pairingProposal.pairingProposer.publicKey, pairingSuccess.responder.publicKey) + assert((pairingSuccess.expiry.seconds - pairingProposal.ttl.seconds) > 0) + assert(pairingSuccess.state == PairingState(null)) + } + + @Test + fun `PairingSuccess mapped to PreSettlementPairing_Approve`() { + val randomId = 1L + val settledTopic = Topic(getRandom64ByteHexString()) + val expiry = Expiry(1) + val pairingProposal = mockk { + every { topic } returns Topic(getRandom64ByteHexString()) + every { relay } returns mockk() + every { pairingProposer } returns PairingProposer("0x123", false) + every { ttl } returns mockk() + } + + val wcPairingApprove = pairingProposal.toApprove(randomId, settledTopic, expiry, PublicKey("0x123")) + assertEquals(randomId, wcPairingApprove.id) + assertEquals(pairingProposal.toPairingSuccess(settledTopic, expiry, PublicKey("0x123")), wcPairingApprove.params) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapterTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapterTest.kt similarity index 84% rename from walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapterTest.kt rename to walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapterTest.kt index d2fc14e91f..1d74019028 100644 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapterTest.kt +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/ExpiryAdapterTest.kt @@ -1,10 +1,10 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Expiry internal class ExpiryAdapterTest { private val moshi = Moshi.Builder() diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapterTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapterTest.kt similarity index 86% rename from walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapterTest.kt rename to walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapterTest.kt index c2e3a28c8a..c505ea703d 100644 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapterTest.kt +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/SubscriptionIdAdapterTest.kt @@ -1,9 +1,9 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.common.SubscriptionId +import com.walletconnect.walletconnectv2.common.SubscriptionId import kotlin.test.assertEquals import kotlin.test.assertNotNull diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TopicAdapterTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TopicAdapterTest.kt similarity index 77% rename from walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TopicAdapterTest.kt rename to walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TopicAdapterTest.kt index f7b2892073..ac251a2e91 100644 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TopicAdapterTest.kt +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TopicAdapterTest.kt @@ -1,12 +1,11 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.common.SubscriptionId -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.util.getRandom64ByteHexString +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.util.getRandom64ByteHexString import kotlin.test.assertNotNull internal class TopicAdapterTest { @@ -31,7 +30,7 @@ internal class TopicAdapterTest { @Test fun toJson() { val topic = Topic(getRandom64ByteHexString()) - val expected = """"${topic.topicValue}"""" + val expected = "\"${topic.value}\"" val topicJson = moshi.adapter(Topic::class.java).toJson(topic) diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TtlAdapterTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TtlAdapterTest.kt similarity index 83% rename from walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TtlAdapterTest.kt rename to walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TtlAdapterTest.kt index 79931bd9c2..03a89aade3 100644 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/network/adapters/TtlAdapterTest.kt +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/common/network/adapters/TtlAdapterTest.kt @@ -1,10 +1,10 @@ -package org.walletconnect.walletconnectv2.common.network.adapters +package com.walletconnect.walletconnectv2.common.network.adapters import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.common.Ttl class TtlAdapterTest { private val moshi = Moshi.Builder() diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodecTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodecTest.kt similarity index 54% rename from walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodecTest.kt rename to walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodecTest.kt index 9682f9bcc3..294baeffb3 100644 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodecTest.kt +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/codec/AuthenticatedEncryptionCodecTest.kt @@ -1,19 +1,20 @@ -package org.walletconnect.walletconnectv2.crypto.codec +package com.walletconnect.walletconnectv2.crypto.codec import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tinder.scarlet.utils.getRawType import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.clientsync.pairing.PairingPayload -import org.walletconnect.walletconnectv2.common.SubscriptionId -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.common.Ttl -import org.walletconnect.walletconnectv2.common.network.adapters.SubscriptionIdAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.TtlAdapter -import org.walletconnect.walletconnectv2.crypto.data.PublicKey -import org.walletconnect.walletconnectv2.util.bytesToHex -import org.walletconnect.walletconnectv2.util.toEncryptionPayload +import com.walletconnect.walletconnectv2.clientsync.pairing.after.PostSettlementPairing +import com.walletconnect.walletconnectv2.common.SubscriptionId +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.common.network.adapters.SubscriptionIdAdapter +import com.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter +import com.walletconnect.walletconnectv2.common.network.adapters.TtlAdapter +import com.walletconnect.walletconnectv2.crypto.data.EncryptionPayload +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey +import com.walletconnect.walletconnectv2.util.bytesToHex import kotlin.test.assertEquals class AuthenticatedEncryptionCodecTest { @@ -22,26 +23,30 @@ class AuthenticatedEncryptionCodecTest { @Test fun `Codec AES_256_CBC and Hmac_SHA256 authentication test`() { - val sharedKey = "94BA14D48AAF8E0D3FA13E94A73C8745136EB7C3D7BA6232E6512A78D6624A04" + val sharedKey = SharedKey("94BA14D48AAF8E0D3FA13E94A73C8745136EB7C3D7BA6232E6512A78D6624A04") val message = "WalletConnect" - val encryptedPayload = codec.encrypt(message, sharedKey, PublicKey("12")) - assertEquals(encryptedPayload.publicKey, "12") - val text = codec.decrypt(encryptedPayload, sharedKey) + val encryptedMessage = + codec.encrypt(message, sharedKey, PublicKey("355957413b1693eea042918f8f346618bfdc29e9d00f2e6bbd702bc29c3e2e4d")) + val payload = encryptedMessage.toEncryptionPayload() + assertEquals(payload.publicKey, "355957413b1693eea042918f8f346618bfdc29e9d00f2e6bbd702bc29c3e2e4d") + val text = codec.decrypt(payload, sharedKey) assertEquals(text, message) } @Test fun `Codec AES_256_CBC and Hmac_SHA256 invalid HMAC test`() { - val sharedKey1 = "94BA14D48AAF8E0D3FA13E94A73C8745136EB7C3D7BA6232E6512A78D6624A04" - val sharedKey2 = "95BA14D48AAF8E0D3FA13E94A73C8745136EB7C3D7BA6232E6512A78D6624A04" + val sharedKey1 = SharedKey("94BA14D48AAF8E0D3FA13E94A73C8745136EB7C3D7BA6232E6512A78D6624A04") + val sharedKey2 = SharedKey("95BA14D48AAF8E0D3FA13E94A73C8745136EB7C3D7BA6232E6512A78D6624A04") val message = "WalletConnect" - val encryptedPayload = codec.encrypt(message, sharedKey1, PublicKey("12")) - assertEquals(encryptedPayload.publicKey, "12") + val encryptedMessage = + codec.encrypt(message, sharedKey1, PublicKey("355957413b1693eea042918f8f346618bfdc29e9d00f2e6bbd702bc29c3e2e4d")) + val payload = encryptedMessage.toEncryptionPayload() + assertEquals(payload.publicKey, "355957413b1693eea042918f8f346618bfdc29e9d00f2e6bbd702bc29c3e2e4d") try { - codec.decrypt(encryptedPayload, sharedKey2) + codec.decrypt(payload, sharedKey2) } catch (e: Exception) { assertEquals("Invalid Hmac", e.message) } @@ -51,11 +56,11 @@ class AuthenticatedEncryptionCodecTest { fun `get auth and hmac keys test`() { val sharedKey = "4b21b43b2e04dbe0105d250f5d72d5d9c28d8de202f240863e268e4ded9e9a6a" - val decryptioKey = "c237bae5d78d52a6a718202fabfaae1cdfb83dd8a54b575c2e2f3e11fb67fa8b" + val decryptionKey = "c237bae5d78d52a6a718202fabfaae1cdfb83dd8a54b575c2e2f3e11fb67fa8b" val hmac = "50e98dc7a1013c3c38f76aaa80dd7ca6c4230a866298415f308c59d4285a6f48" - val (decryp, auth) = codec.getKeys(sharedKey) + val (decrypt, auth) = codec.getKeys(sharedKey) - assertEquals(decryp.bytesToHex(), decryptioKey) + assertEquals(decrypt.bytesToHex(), decryptionKey) assertEquals(auth.bytesToHex(), hmac) } @@ -70,7 +75,7 @@ class AuthenticatedEncryptionCodecTest { assertEquals(payload.publicKey.length, 64) assertEquals(payload.mac.length, 64) - val sharedKey = "b426d6b8b7a57930cae8870179864849d6e89f1e8e801f7ca9a50bc2384ee043" + val sharedKey = SharedKey("b426d6b8b7a57930cae8870179864849d6e89f1e8e801f7ca9a50bc2384ee043") val json = codec.decrypt(payload, sharedKey) @@ -84,9 +89,25 @@ class AuthenticatedEncryptionCodecTest { } }.addLast(KotlinJsonAdapterFactory()).build() - val request: PairingPayload? = - moshi.adapter(PairingPayload::class.java).fromJson(json) + val request: PostSettlementPairing.PairingPayload? = + moshi.adapter(PostSettlementPairing.PairingPayload::class.java).fromJson(json) - assertEquals(request?.params?.request?.params?.proposer?.publicKey, "37d8c448a2241f21550329f451e8c1901e7dad5135ade604f1e106437843037f") + assertEquals( + request?.params?.request?.params?.proposer?.publicKey, + "37d8c448a2241f21550329f451e8c1901e7dad5135ade604f1e106437843037f" + ) } +} + +fun String.toEncryptionPayload(): EncryptionPayload { + val pubKeyStartIndex = EncryptionPayload.ivLength + val macStartIndex = pubKeyStartIndex + EncryptionPayload.publicKeyLength + val cipherTextStartIndex = macStartIndex + EncryptionPayload.macLength + + val iv = this.substring(0, pubKeyStartIndex) + val publicKey = this.substring(pubKeyStartIndex, macStartIndex) + val mac = this.substring(macStartIndex, cipherTextStartIndex) + val cipherText = this.substring(cipherTextStartIndex, this.length) + + return EncryptionPayload(iv, publicKey, mac, cipherText) } \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/managers/BouncyCastleCryptoManagerTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/managers/BouncyCastleCryptoManagerTest.kt new file mode 100644 index 0000000000..3e6033788a --- /dev/null +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/managers/BouncyCastleCryptoManagerTest.kt @@ -0,0 +1,104 @@ +package com.walletconnect.walletconnectv2.crypto.managers + +import io.mockk.spyk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.crypto.data.Key +import com.walletconnect.walletconnectv2.crypto.data.PrivateKey +import com.walletconnect.walletconnectv2.crypto.data.PublicKey +import com.walletconnect.walletconnectv2.crypto.data.SharedKey +import com.walletconnect.walletconnectv2.storage.KeyStore +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +internal class BouncyCastleCryptoManagerTest { + private val privateKeyString = "BCA8EF78C5D69A3681E87A0630E16AC374B6ED612EDAB1BD26F02C4B2499851E".lowercase() + private val publicKeyString = "DC22D30CFB89E30A356BA86EE48F66F1722C9B32CC9C0666A47748376BEC177D".lowercase() + private val privateKey = PrivateKey(privateKeyString) + private val publicKey = PublicKey(publicKeyString) + private val keyChain: KeyStore = KeyChainMock() + private val sut = spyk(BouncyCastleCryptoManager(keyChain), recordPrivateCalls = true) + + @BeforeEach + fun setUp() { + sut.setKeyPair(publicKey, privateKey) + } + + @Test + fun `Verify that the generated public key has valid length`() { + val publicKey = sut.generateKeyPair() + + val test = "7d819cb5192e0f18aa4394b72d24d2a02df773a0eb56a990e9adcdb16db39e7b" + assert(test.length == 64) + assert(publicKey.keyAsHex.length == 64) + } + + @Test + fun `Generate shared key test`() { + val result = sut.getSharedKey( + PrivateKey("1fb63fca5c6ac731246f2f069d3bc2454345d5208254aa8ea7bffc6d110c8862"), + PublicKey("590c2c627be7af08597091ff80dd41f7fa28acd10ef7191d7e830e116d3a186a") + ) + + assert(result.length == 64) + + assertEquals( + "9c87e48e69b33a613907515bcd5b1b4cc10bbaf15167b19804b00f0a9217e607", + result.lowercase() + ) + } + + + @Test + fun `Generate a shared key and return a Topic object`() { + val peerKey = PublicKey("D083CDBBD08B93BD9AD10E95712DC0D4BD880401B04D587D8D3782FEA0CD31A9".lowercase()) + + val (sharedKey, topic) = sut.generateTopicAndSharedKey(publicKey, peerKey) + + assert(topic.value.isNotBlank()) + assert(topic.value.length == 64) + assert(sharedKey.keyAsHex.length == 64) + + assertEquals(topic.value, "7c3adceb58cce0035cb5f4100b5980000dbba4a920b1da1568377fc4e2c3ab2b") + assertEquals(sharedKey.keyAsHex, "af2be9138502d5a2127e691670993e4007337236e9d182fdcf654f2b5bee2038") + } + + @Test + fun `Generate a Topic with a sharedKey and a public key and no existing topic`() { + val sharedKeyString = SharedKey("D083CDBBD08B93BD9AD10E95712DC0D4BD880401B04D587D8D3782FEA0CD31A9".lowercase()) + val sharedKey = object : Key { + override val keyAsHex: String = sharedKeyString.keyAsHex + } + sut.setEncryptionKeys(sharedKeyString, publicKey, Topic("topic")) + + assertEquals(sharedKey.keyAsHex, keyChain.getKeys(Topic("topic").value).first) + assertEquals(publicKey.keyAsHex, keyChain.getKeys(Topic("topic").value).second) + } + + @Test + fun `SetKeyPair sets the concatenated keys to storage`() { + sut.setKeyPair(publicKey, privateKey) + + assertNotNull(sut.getKeyPair(publicKey)) + assertEquals(publicKeyString, keyChain.getKeys(publicKeyString).first) + assertEquals(privateKeyString, keyChain.getKeys(publicKeyString).second) + } + + @Test + fun `GetKeyPair gets a pair of PublicKey and PrivateKey when using a PublicKey as the key`() { + val (testPublicKey, testPrivateKey) = sut.getKeyPair(publicKey) + + assertEquals(publicKey.keyAsHex, testPublicKey.keyAsHex) + assertEquals(privateKey.keyAsHex, testPrivateKey.keyAsHex) + } + + @Test + fun `ConcatKeys takes two keys and returns a string of the two keys combined`() { + sut.setKeyPair(publicKey, privateKey) + val (public, private) = sut.getKeyPair(publicKey) + + assertEquals(publicKeyString, public.keyAsHex) + assertEquals(privateKeyString, private.keyAsHex) + } +} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/managers/KeyChainMock.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/managers/KeyChainMock.kt new file mode 100644 index 0000000000..0322ee1811 --- /dev/null +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/crypto/managers/KeyChainMock.kt @@ -0,0 +1,34 @@ +package com.walletconnect.walletconnectv2.crypto.managers + +import com.walletconnect.walletconnectv2.crypto.data.Key +import com.walletconnect.walletconnectv2.storage.KeyStore +import com.walletconnect.walletconnectv2.util.bytesToHex +import com.walletconnect.walletconnectv2.util.hexToBytes + +class KeyChainMock : KeyStore { + + private val mapOfKeys = mutableMapOf() + + override fun setKey(tag: String, key1: Key, key2: Key) { + val keys = concatKeys(key1, key2) + mapOfKeys[tag] = keys + } + + override fun getKeys(tag: String): Pair { + val keys = mapOfKeys[tag] ?: "" + return splitKeys(keys) + } + + override fun deleteKeys(tag: String) { + mapOfKeys.remove(tag) + } + + private fun concatKeys(keyA: Key, keyB: Key): String = (keyA.keyAsHex.hexToBytes() + keyB.keyAsHex.hexToBytes()).bytesToHex() + + private fun splitKeys(concatKeys: String): Pair { + val concatKeysByteArray = concatKeys.hexToBytes() + val privateKeyByteArray = concatKeysByteArray.sliceArray(0 until (concatKeysByteArray.size / 2)) + val publicKeyByteArray = concatKeysByteArray.sliceArray((concatKeysByteArray.size / 2) until concatKeysByteArray.size) + return privateKeyByteArray.bytesToHex() to publicKeyByteArray.bytesToHex() + } +} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/relay/WakuRelayRepositoryTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/relay/WakuRelayRepositoryTest.kt new file mode 100644 index 0000000000..e12b0864e6 --- /dev/null +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/relay/WakuRelayRepositoryTest.kt @@ -0,0 +1,67 @@ +package com.walletconnect.walletconnectv2.relay + +import android.app.Application +import io.mockk.coEvery +import io.mockk.spyk +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.flowOf +import org.json.JSONObject +import org.junit.Rule +import org.junit.jupiter.api.Test +import com.walletconnect.walletconnectv2.clientsync.pairing.Pairing +import com.walletconnect.walletconnectv2.clientsync.pairing.before.PreSettlementPairing +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingParticipant +import com.walletconnect.walletconnectv2.clientsync.pairing.before.success.PairingState +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.relay.waku.Relay +import com.walletconnect.walletconnectv2.relay.waku.WakuNetworkRepository +import com.walletconnect.walletconnectv2.util.CoroutineTestRule +import com.walletconnect.walletconnectv2.util.getRandom64ByteHexString +import com.walletconnect.walletconnectv2.util.runTest +import kotlin.test.assertEquals + +@ExperimentalCoroutinesApi +internal class WakuRelayRepositoryTest { + + @get:Rule + val coroutineTestRule = CoroutineTestRule() + + private val relayFactory = WakuNetworkRepository.WakuNetworkFactory(true, "127.0.0.1", "", Application()) + private val sut = spyk(WakuNetworkRepository.init(relayFactory)) + + @Test + fun `Publish a pairing request, expect a successful acknowledgement`() { + // Arrange + val topic = Topic(getRandom64ByteHexString()) + val settledTopic = Topic(getRandom64ByteHexString()) + val preSettlementPairing = PreSettlementPairing.Approve( + id = 1L, + params = Pairing.Success( + settledTopic = settledTopic, + relay = JSONObject(), + responder = PairingParticipant(getRandom64ByteHexString()), + expiry = Expiry(1), + state = PairingState() + ) + ) + coEvery { sut.observePublishAcknowledgement } returns flowOf( + Relay.Publish.Acknowledgement( + id = preSettlementPairing.id, + result = true + ) + ) + + // Act + sut.publish(topic, preSettlementPairing.toString()) + + // Assert + coroutineTestRule.runTest { + sut.observePublishAcknowledgement.collect { + assertEquals(preSettlementPairing.id, it.id) + assertEquals(true, it.result) + } + } + } +} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/relay/data/model/RelayTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/relay/data/model/RelayTest.kt similarity index 79% rename from walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/relay/data/model/RelayTest.kt rename to walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/relay/data/model/RelayTest.kt index 38a932ecf5..bff3e7c3d7 100644 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/relay/data/model/RelayTest.kt +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/relay/data/model/RelayTest.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.relay.data.model +package com.walletconnect.walletconnectv2.relay.data.model import com.squareup.moshi.Moshi import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory @@ -27,16 +27,17 @@ import org.junit.Rule import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.common.Expiry -import org.walletconnect.walletconnectv2.common.SubscriptionId -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.common.Ttl -import org.walletconnect.walletconnectv2.common.network.adapters.* -import org.walletconnect.walletconnectv2.relay.data.RelayService -import org.walletconnect.walletconnectv2.util.CoroutineTestRule -import org.walletconnect.walletconnectv2.util.adapters.FlowStreamAdapter -import org.walletconnect.walletconnectv2.util.getRandom64ByteHexString -import org.walletconnect.walletconnectv2.util.runTest +import com.walletconnect.walletconnectv2.common.Expiry +import com.walletconnect.walletconnectv2.common.SubscriptionId +import com.walletconnect.walletconnectv2.common.Topic +import com.walletconnect.walletconnectv2.common.Ttl +import com.walletconnect.walletconnectv2.common.network.adapters.* +import com.walletconnect.walletconnectv2.relay.waku.Relay +import com.walletconnect.walletconnectv2.relay.waku.RelayService +import com.walletconnect.walletconnectv2.util.CoroutineTestRule +import com.walletconnect.walletconnectv2.util.adapters.FlowStreamAdapter +import com.walletconnect.walletconnectv2.util.getRandom64ByteHexString +import com.walletconnect.walletconnectv2.util.runTest import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertIs @@ -57,7 +58,7 @@ internal class RelayTest { private lateinit var serverEventObserver: TestStreamObserver private lateinit var client: RelayService - private lateinit var clientEventObserver: TestStreamObserver +// private lateinit var clientEventObserver: TestStreamObserver @BeforeEach fun setUp() { @@ -104,10 +105,10 @@ internal class RelayTest { server.sendPublishAcknowledgement(relayPublishAcknowledgement) // Assert - clientEventObserver.awaitValues( - any>(), - any().containingRelayObject(relayPublishAcknowledgement) - ) +// clientEventObserver.awaitValues( +// any>(), +// any().containingRelayObject(relayPublishAcknowledgement) +// ) coroutineRule.runTest { val actualPublishAcknowledgement = clientRelayPublishObserver.singleOrNull() @@ -155,10 +156,10 @@ internal class RelayTest { server.sendSubscribeAcknowledgement(relaySubscribeAcknowledgement) // Assert - clientEventObserver.awaitValues( - any>(), - any().containingRelayObject(relaySubscribeAcknowledgement) - ) +// clientEventObserver.awaitValues( +// any>(), +// any().containingRelayObject(relaySubscribeAcknowledgement) +// ) coroutineRule.runTest { val actualSubscribeAcknowledgement = clientRelaySubscribeObserver.singleOrNull() @@ -197,6 +198,8 @@ internal class RelayTest { @Test fun `Server sends Relay_Subscription_Request, should be received by the client`() { + val message = + "ffbecf819a49a266b262309ad269ae4016ef8b8ef1f010d4447b7e089aac0b943d5e2ca94646ddcfa92f4e8e5778cc3e39e3e876dd95065c5899b95a98512664a8c77853c47d31c2e714e50018f3d1b525dbd2f76cde5bff8b261f343ecb3d956ad9e74819c8729fa1c77be4b5fb7d39ccc697bda421fb90d11315d828e79fca6a27316d3b09f14c7f3483b25b000820e7b64a75e5f59216e5f0ecbc4ec20c53664ad5e967026aa119a32a655e3ff3e110ca4c7e629b845b8ecf7ea6f296a79a6de3dc5794c3a51059bb08b09974501ffcf2d7fddafafd9f1b22e97b6abbb6bcd978a8a87341f33bc662c101947a06c72f6c7709a0a612f46fcd8b5fbce0bdd4c56ca330e6e2802fbf6e3830210f3c1b626863de93fd02857c615436e1b9dc7d36d45bbec8acfb24cd45c46946832d5a7cc20334fd7405dba997daf4725bc849450f197e7e9e2f5e20839ba1f77895b3cbccc279fdc0a9d40156a28ad2adcd6a8afc68f9735c4e7c22c49caf5150f243bab702a71699c9b26420668c81fc5b311488331a4456ba1baf619818b4ecfe6f6de8f80dc42a85c785aa78dd187e82faec549780051551335c651af10f89a3e37103e56a8ebf27f3054e4303a6bce88d7c082bfda897facfd952df5d3d6776370884cb04923c804c99059bb269fdbff3543d89648f39a7cc6fdad61ea0f24deeab420bc65dde6c7a6a3f5fe3775fe4a95a8bf8b70ae946696c808206baf119f0b3142d502c7ca0c102548a1263de2c04bde47aa1a716ae7b00959e300b56d6f0595d1588e07c618b914e3c76cb7d103cd8c6b91ed0aaadc2c129455c07905e5272ea4039660cb8e53a64101dae6e8737a082ac9a9b531a4cbc83e009c1722ca108a26bd193817392890b80cf519f2f14e1fc0e1b47d0b7da47d0635eace28e42456a222da5f2044895914a0b21568d49c222f55b114a558649f094012dbaaabd02ad1aae591d80b8754bb39964f4b9c235166b1ea5c80eb9870e90f073722926f823e5ca72714de10f6f4ed4072bfd3ffc4d32ec0e920edb404b7b1afa1f001d18948fe25562c9b8d52824a4fad20082f28a13e96b7277cb4e7a5ccbbf8095293892b2bac008fcee038765743fb9688abf8affd2477f7de90494ccbba94f6a88a0e0c215d5134b70f41f28754e1b236ab43ec65696fa182fa9525a70e7f42141ec38cfe57d26230b3d520ba2769517c9f8f43a161d38438079b967ab73835865b68a22d3cde7a37fccad1ee3f33ae13bb0f09b4b86ce2ee07823ba793a0fafee" // Arrange val relaySubscriptionRequest = Relay.Subscription.Request( id = 1, @@ -204,7 +207,7 @@ internal class RelayTest { subscriptionId = SubscriptionId("subscriptionId"), subscriptionData = Relay.Subscription.Request.Params.SubscriptionData( topic = Topic(getRandom64ByteHexString()), - message = "This is a test" + message = message ) ) ) @@ -214,10 +217,10 @@ internal class RelayTest { server.sendSubscriptionRequest(relaySubscriptionRequest) // Assert - clientEventObserver.awaitValues( - any>(), - any().containingRelayObject(relaySubscriptionRequest) - ) +// clientEventObserver.awaitValues( +// any>(), +// any().containingRelayObject(relaySubscriptionRequest) +// ) coroutineRule.runTest { val actualRelaySubscriptionRequest = clientRelaySubscriptionObserver.singleOrNull() @@ -236,7 +239,7 @@ internal class RelayTest { val serverRelaySubscriptionObserver = server.observeSubscriptionAcknowledgement().test() // Act - client.subscriptionAcknowledgement(relaySubscriptionAcknowledgement) + client.publishSubscriptionAcknowledgement(relaySubscriptionAcknowledgement) // Assert serverEventObserver.awaitValues( @@ -288,10 +291,10 @@ internal class RelayTest { server.sendUnsubscribeAcknowledgement(relayUnsubscribeAcknowledgement) // Assert - clientEventObserver.awaitValues( - any>(), - any().containingRelayObject(relayUnsubscribeAcknowledgement) - ) +// clientEventObserver.awaitValues( +// any>(), +// any().containingRelayObject(relayUnsubscribeAcknowledgement) +// ) coroutineRule.runTest { val actualSubscribeAcknowledgement = clientRelayUnsubscribeObserver.singleOrNull() @@ -323,7 +326,7 @@ internal class RelayTest { server = createServer() serverEventObserver = server.observeEvents().test() client = createClient() - clientEventObserver = client.observeEvents().test() +// clientEventObserver = client.observeEvents().test() } private fun createServer(): MockServerService = Scarlet.Builder() @@ -350,9 +353,9 @@ internal class RelayTest { serverEventObserver.awaitValues( any>() ) - clientEventObserver.awaitValues( - any>() - ) +// clientEventObserver.awaitValues( +// any>() +// ) } internal interface MockServerService { diff --git a/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/storage/StorageRepositoryTest.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/storage/StorageRepositoryTest.kt new file mode 100644 index 0000000000..85b9ab06b1 --- /dev/null +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/storage/StorageRepositoryTest.kt @@ -0,0 +1,25 @@ +package com.walletconnect.walletconnectv2.storage + +import android.app.Application +import androidx.test.core.app.ApplicationProvider +import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver +import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.junit.Rule +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.walletconnect.walletconnectv2.Database +import com.walletconnect.walletconnectv2.util.CoroutineTestRule + +@ExperimentalCoroutinesApi +@RunWith(RobolectricTestRunner::class) +internal class StorageRepositoryTest { + + @get:Rule + val coroutineTestRule = CoroutineTestRule() + + private val app = ApplicationProvider.getApplicationContext() + private val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY).apply { + Database.Schema.create(this) + } + private val storageRepository = StorageRepository(driver, app) +} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/util/CoroutineTestRule.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/util/CoroutineTestRule.kt similarity index 95% rename from walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/util/CoroutineTestRule.kt rename to walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/util/CoroutineTestRule.kt index de86cca56c..82cb7a514c 100644 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/util/CoroutineTestRule.kt +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/util/CoroutineTestRule.kt @@ -1,4 +1,4 @@ -package org.walletconnect.walletconnectv2.util +package com.walletconnect.walletconnectv2.util import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -13,13 +13,13 @@ class CoroutineTestRule(val testDispatcher: TestCoroutineDispatcher = TestCorout override fun finished(description: Description?) { super.finished(description) - Dispatchers.setMain(testDispatcher) + Dispatchers.resetMain() + testDispatcher.cleanupTestCoroutines() } override fun starting(description: Description?) { super.starting(description) - Dispatchers.resetMain() - testDispatcher.cleanupTestCoroutines() + Dispatchers.setMain(testDispatcher) } } diff --git a/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/util/UtilFunctions.kt b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/util/UtilFunctions.kt new file mode 100644 index 0000000000..145762cc70 --- /dev/null +++ b/walletconnectv2/src/test/kotlin/com/walletconnect/walletconnectv2/util/UtilFunctions.kt @@ -0,0 +1,6 @@ +package com.walletconnect.walletconnectv2.util + +private const val STRING_LENGTH = 64 +private val CHAR_POOL: List = ('A'..'F') + ('0'..'9') + +internal fun getRandom64ByteHexString() = (1..STRING_LENGTH).map { CHAR_POOL.random() }.joinToString("") \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/integration/PairingIntegrationTest.kt b/walletconnectv2/src/test/kotlin/integration/PairingIntegrationTest.kt deleted file mode 100644 index ef48a761c3..0000000000 --- a/walletconnectv2/src/test/kotlin/integration/PairingIntegrationTest.kt +++ /dev/null @@ -1,115 +0,0 @@ -package integration - -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.collect -import org.walletconnect.walletconnectv2.engine.EngineInteractor -import java.time.Duration -import kotlin.system.exitProcess - -//TODO fix integration test -fun main() { -// pairTest() -// approveSessionTest() -} - -//private fun pairTest() { -// val job = SupervisorJob() -// val scope = CoroutineScope(job + Dispatchers.IO) -// val engine = EngineInteractor(true, "relay.walletconnect.org") -// val uri = -// "wc:5e92f91290c5cf408fd099c7ae5d74bb3743d41ae4313c650611e3ea07e2af3f@2?controller=false&publicKey=36e3df2b8dffad98b2bce12eacbd6d37015bc436ed21dad18b72d9e3d2232c2b&relay=%7B%22protocol%22%3A%22waku%22%7D" -// scope.launch { -// engine.pair(uri) -// -// try { -// withTimeout(Duration.ofMinutes(20).toMillis()) { -// val pairDeferred = async(Dispatchers.IO) { -// engine.publishAcknowledgement.collect { -// println("Publish Acknowledgement: $it") -// require(it.result) { -// "Acknowledgement from Relay returned false" -// } -// } -// } -// -// val subscribeDeferred = async(Dispatchers.IO) { -// engine.subscribeAcknowledgement.collect { -// println("Subscribe Acknowledgement $it") -// require(it.result.id.isNotBlank()) { -// "Acknowledgement from Relay returned false" -// } -// } -// } -// -// val subscriptionDeferred = async(Dispatchers.IO) { -// engine.subscriptionRequest.collect { -// println("Subscription Request $it") -// } -// } -// -// val sessionProposalDeferred = async(Dispatchers.IO) { -// engine.sessionProposal.collect { -// println("Session Proposal: $it") -// } -// } -// -// listOf(pairDeferred, subscribeDeferred, subscriptionDeferred, sessionProposalDeferred).awaitAll() -// } -// } catch (timeoutException: TimeoutCancellationException) { -// println("timed out") -// exitProcess(0) -// } -// } -//} - -//fun approveSessionTest() { -// val job = SupervisorJob() -// val scope = CoroutineScope(job + Dispatchers.IO) -// val engine = EngineInteractor(true, "relay.walletconnect.org") -// -// engine.approve( -// Session.Proposal( -// topic = Topic("69bba8737e4c7d8715b0ea92fe044ba291a359c24a3cde3e240bc8ec81fa0757"), -// relay = RelayProtocolOptions(protocol = "waku"), -// proposer = SessionProposer( -// publicKey = "fa75874568a6f347229c5936f34ac7e2117f5233e13e3e418332687acd56382c", -// controller = false, null -// ), -// signal = SessionSignal(params = SessionSignal.Params(topic = Topic("15a66762d0a589a2330c73a627a8b83668f3b1eb7f172da1bedf045e09108aec"))), -// permissions = SessionProposedPermissions( -// blockchain = SessionProposedPermissions.Blockchain(chains = listOf("eip155:42")), -// jsonRpc = SessionProposedPermissions.JsonRpc( -// methods = listOf( -// "eth_sendTransaction", -// "personal_sign", -// "eth_signTypedData" -// ) -// ), -// notifications = SessionProposedPermissions.Notifications(types = listOf()) -// ), -// ttl = Ttl(604800) -// ) -// ) -// -// scope.launch { -// try { -// -// withTimeout(Duration.ofMinutes(1).toMillis()) { -// -// val pairDeferred = async(Dispatchers.IO) { -// engine.publishAcknowledgement.collect { -// println("Publish Acknowledgement: $it") -// require(it.result) { -// "Acknowledgement from Relay returned false" -// } -// } -// } -// -// pairDeferred.await() -// } -// } catch (timeoutException: TimeoutCancellationException) { -// println("timed out") -// exitProcess(0) -// } -// } -//} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/MappingFunctionsTest.kt b/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/MappingFunctionsTest.kt deleted file mode 100644 index 5ac7fd5f05..0000000000 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/common/MappingFunctionsTest.kt +++ /dev/null @@ -1,106 +0,0 @@ -package org.walletconnect.walletconnectv2.common - -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.Moshi -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory -import io.mockk.every -import io.mockk.mockk -import org.json.JSONObject -import org.junit.jupiter.api.Assertions.assertFalse -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.clientsync.PreSettlementPairing -import org.walletconnect.walletconnectv2.clientsync.pairing.Pairing -import org.walletconnect.walletconnectv2.clientsync.pairing.proposal.PairingProposer -import org.walletconnect.walletconnectv2.clientsync.pairing.success.PairingParticipant -import org.walletconnect.walletconnectv2.clientsync.pairing.success.PairingState -import org.walletconnect.walletconnectv2.common.network.adapters.ExpiryAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.JSONObjectAdapter -import org.walletconnect.walletconnectv2.common.network.adapters.TopicAdapter -import org.walletconnect.walletconnectv2.crypto.data.PublicKey -import org.walletconnect.walletconnectv2.util.getRandom64ByteHexString -import kotlin.test.assertEquals - -internal class MappingFunctionsTest { - - @Test - fun `A proper URI mapped to a PairingProposal`() { - val testUri = - "wc:0b1a3d6c0336662dddb6278ee0aa25380569b79e7e86cfe39fb20b4b189096a0@2?controller=false&publicKey=66db1bd5fad65392d1d5a4856d0d549d2fca9194327138b41c289b961d147860&relay=%7B%22protocol%22%3A%22waku%22%7D" - - val pairingProposal = testUri.toPairProposal() - - assertNotNull(pairingProposal) - assert(pairingProposal.topic.topicValue.isNotBlank()) - assert(pairingProposal.pairingProposer.publicKey.isNotBlank()) - assert(pairingProposal.ttl.seconds > 0) - } - - @Test - fun `PairingProposal mapped to PairingSuccess`() { - val pairingProposal = mockk() { - every { topic } returns Topic("0x111") - every { relay } returns mockk() - every { pairingProposer } returns PairingProposer("0x123", false) - every { ttl } returns Ttl(2L) - } - - val pairingSuccess = pairingProposal.toPairingSuccess( - Topic("0x111"), - Expiry(10L), - PublicKey("0x123") - ) - - assertEquals(pairingProposal.topic, pairingSuccess.settledTopic) - assertEquals(pairingProposal.relay, pairingSuccess.relay) - assertEquals(pairingProposal.pairingProposer.publicKey, pairingSuccess.responder.publicKey) - assert((pairingSuccess.expiry.seconds - pairingProposal.ttl.seconds) > 0) - assert(pairingSuccess.state == PairingState(null)) - } - - @Test - fun `PairingSuccess mapped to PreSettlementPairing_Approve`() { - val randomId = 1L - val settledTopic = Topic(getRandom64ByteHexString()) - val expiry = Expiry(1) - val pairingProposal = mockk() { - every { topic } returns Topic(getRandom64ByteHexString()) - every { relay } returns mockk() - every { pairingProposer } returns PairingProposer("0x123", false) - every { ttl } returns mockk() - } - - val wcPairingApprove = pairingProposal.toApprove( - randomId, - settledTopic, - expiry, - PublicKey("0x123") - ) - - assertEquals(randomId, wcPairingApprove.id) - assertEquals(pairingProposal.toPairingSuccess(settledTopic, expiry, PublicKey("0x123")), wcPairingApprove.params) - } - - @Test - fun `PreSettlementPairing_Approve to RelayPublish_Request`() { - val moshi = Moshi.Builder() - .addLast(ExpiryAdapter as JsonAdapter) - .addLast(TopicAdapter as JsonAdapter) - .addLast(JSONObjectAdapter as JsonAdapter) - .addLast(KotlinJsonAdapterFactory()) - .build() - val preSettlementPairingApprove = mockk() { - every { id } returns 1 - every { jsonrpc } returns "2.0" - every { method } returns "wc_pairingApprove" - every { params } returns Pairing.Success(Topic(getRandom64ByteHexString()) /*settle topic*/, JSONObject(), PairingParticipant(getRandom64ByteHexString()), Expiry(100L), PairingState(null)) - } - - //TODO test failing, review - val relayPublishRequest = preSettlementPairingApprove.toRelayPublishRequest(1, Topic(getRandom64ByteHexString()), moshi) - - assert(relayPublishRequest.params.message.isNotBlank()) - assertFalse(relayPublishRequest.params.message.contains(" ")) - assertFalse(relayPublishRequest.params.message.contains(",")) - } -} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/crypto/managers/LazySodiumCryptoManagerTest.kt b/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/crypto/managers/LazySodiumCryptoManagerTest.kt deleted file mode 100644 index 3a2c8814bb..0000000000 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/crypto/managers/LazySodiumCryptoManagerTest.kt +++ /dev/null @@ -1,135 +0,0 @@ -package org.walletconnect.walletconnectv2.crypto.managers - -import com.goterl.lazysodium.utils.HexMessageEncoder -import com.goterl.lazysodium.utils.Key -import io.mockk.spyk -import io.mockk.verify -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.crypto.KeyChain -import org.walletconnect.walletconnectv2.crypto.data.PrivateKey -import org.walletconnect.walletconnectv2.crypto.data.PublicKey -import kotlin.test.assertContentEquals -import kotlin.test.assertEquals -import kotlin.test.assertNotNull - -internal class LazySodiumCryptoManagerTest { - private val privateKeyString = - "BCA8EF78C5D69A3681E87A0630E16AC374B6ED612EDAB1BD26F02C4B2499851E" - private val publicKeyString = "DC22D30CFB89E30A356BA86EE48F66F1722C9B32CC9C0666A47748376BEC177D" - private val privateKey = PrivateKey(privateKeyString) - private val publicKey = PublicKey(publicKeyString) - private val keyChain = object : KeyChain { - val mapOfKeys = mutableMapOf() - - override fun setKey(key: String, value: String) { - mapOfKeys[key] = value - } - - override fun getKey(key: String): String { - return mapOfKeys[key]!! - } - } - private val sut = spyk(LazySodiumCryptoManager(keyChain), recordPrivateCalls = true) - - @BeforeEach - fun setUp() { - keyChain.setKey(publicKeyString, sut.concatKeys(publicKey, privateKey)) - } - - @Test - fun `Verify that the generated public key is a valid key`() { - val publicKey = sut.generateKeyPair() - val expectedKey = Key.fromHexString(publicKey.keyAsHex) - - assert(publicKey.keyAsHex.length == 64) - assertEquals(expectedKey.asHexString.lowercase(), publicKey.keyAsHex) - } - - @Test - fun `Generate a shared key and return a Topic object`() { - val peerKey = PublicKey("D083CDBBD08B93BD9AD10E95712DC0D4BD880401B04D587D8D3782FEA0CD31A9") - - val (sharedKey, topic) = sut.generateTopicAndSharedKey(publicKey, peerKey) - - assert(topic.topicValue.isNotBlank()) - assert(topic.topicValue.length == 64) - } - - @Test - fun `Generate a Topic with a sharedKey and a public key and no existing topic`() { - val sharedKeyString = "D083CDBBD08B93BD9AD10E95712DC0D4BD880401B04D587D8D3782FEA0CD31A9" - val sharedKey = object : org.walletconnect.walletconnectv2.crypto.data.Key { - override val keyAsHex: String = sharedKeyString - } - val topic = sut.setEncryptionKeys(sharedKeyString, publicKey, null) - - assertEquals(sut.concatKeys(sharedKey, publicKey), keyChain.getKey(topic.topicValue)) - } - - @Test - fun `SetKeyPair sets the concatenated keys to storage`() { - sut.setKeyPair(publicKey, privateKey) - - assertNotNull(keyChain.mapOfKeys[publicKeyString]) - assertEquals(publicKeyString + privateKeyString, keyChain.mapOfKeys[publicKeyString]) - - verify { - sut.concatKeys(publicKey, privateKey) - } - } - - @Test - fun `GetKeyPair gets a pair of PublicKey and PrivateKey when using a PublicKey as the key`() { - val (testPublicKey, testPrivateKey) = sut.getKeyPair(publicKey) - - assertEquals(publicKey, testPublicKey) - assertEquals(privateKey, testPrivateKey) - } - - @Test - fun `ConcatKeys takes two keys and returns a string of the two keys combined`() { - val concatString = sut.concatKeys(publicKey, privateKey) - - assertEquals(publicKeyString + privateKeyString, concatString) - assertContentEquals( - HexMessageEncoder().decode(concatString), - concatString.hexStringToByteArray() - ) - } - - @Test - fun `Split a concatenated key into a pair of keys`() { - val concatKeys = sut.concatKeys(publicKey, privateKey) - - val (splitPublicKey, splitPrivateKey) = sut.splitKeys(concatKeys) - - assertEquals(publicKeyString, splitPublicKey.keyAsHex) - assertEquals(privateKeyString, splitPrivateKey.keyAsHex) - } - - @Test - fun `Decrypt Payload`() { - val result = sut.getSharedKeyUsingPrivate( - PrivateKey("1fb63fca5c6ac731246f2f069d3bc2454345d5208254aa8ea7bffc6d110c8862"), - PublicKey("590c2c627be7af08597091ff80dd41f7fa28acd10ef7191d7e830e116d3a186a") - ) - - assertEquals("9c87e48e69b33a613907515bcd5b1b4cc10bbaf15167b19804b00f0a9217e607", result.lowercase()) - } - - private fun String.hexStringToByteArray(): ByteArray { - val hexChars = "0123456789ABCDEF" - val result = ByteArray(length / 2) - - for (i in 0 until length step 2) { - val firstIndex = hexChars.indexOf(this[i]); - val secondIndex = hexChars.indexOf(this[i + 1]); - - val octet = firstIndex.shl(4).or(secondIndex) - result[i.shr(1)] = octet.toByte() - } - - return result - } -} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/relay/WakuRelayRepositoryTest.kt b/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/relay/WakuRelayRepositoryTest.kt deleted file mode 100644 index 7759b7ee60..0000000000 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/relay/WakuRelayRepositoryTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.walletconnect.walletconnectv2.relay - -import io.mockk.coEvery -import io.mockk.spyk -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.flowOf -import org.json.JSONObject -import org.junit.Rule -import org.junit.jupiter.api.Test -import org.walletconnect.walletconnectv2.clientsync.PreSettlementPairing -import org.walletconnect.walletconnectv2.clientsync.pairing.Pairing -import org.walletconnect.walletconnectv2.clientsync.pairing.success.PairingParticipant -import org.walletconnect.walletconnectv2.clientsync.pairing.success.PairingState -import org.walletconnect.walletconnectv2.common.Expiry -import org.walletconnect.walletconnectv2.common.Topic -import org.walletconnect.walletconnectv2.relay.data.model.Relay -import org.walletconnect.walletconnectv2.util.CoroutineTestRule -import org.walletconnect.walletconnectv2.util.getRandom64ByteHexString -import org.walletconnect.walletconnectv2.util.runTest -import kotlin.test.assertEquals - -@ExperimentalCoroutinesApi -internal class WakuRelayRepositoryTest { -// TODO: WakuRelayRepository now needs application. Need to either add Robolectric or move to androidTest - -// @get:Rule -// val coroutineTestRule = CoroutineTestRule() -// -// private val sut = spyk(WakuRelayRepository.initRemote(hostName = "127.0.0.1", apiKey = "")) -// -// @Test -// fun `Publish a pairing request, expect a successful acknowledgement`() { -// // Arrange -// val topic = Topic(getRandom64ByteHexString()) -// val settledTopic = Topic(getRandom64ByteHexString()) -// val preSettlementPairing = PreSettlementPairing.Approve( -// id = 1L, -// params = Pairing.Success( -// settledTopic = settledTopic, -// relay = JSONObject(), -// responder = PairingParticipant(getRandom64ByteHexString()), -// expiry = Expiry(1), -// state = PairingState() -// ) -// ) -// coEvery { sut.publishAcknowledgement } returns flowOf(Relay.Publish.Acknowledgement(id = preSettlementPairing.id, result = true)) -// -// // Act -// sut.publishPairingApproval(topic, preSettlementPairing) -// -// // Assert -// coroutineTestRule.runTest { -// sut.publishAcknowledgement.collect { -// assertEquals(preSettlementPairing.id, it.id) -// assertEquals(true, it.result) -// } -// } -// } -} \ No newline at end of file diff --git a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/util/UtilFunctions.kt b/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/util/UtilFunctions.kt deleted file mode 100644 index c849bcb354..0000000000 --- a/walletconnectv2/src/test/kotlin/org/walletconnect/walletconnectv2/util/UtilFunctions.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.walletconnect.walletconnectv2.util - -import org.walletconnect.walletconnectv2.relay.WakuRelayRepository - -private const val STRING_LENGTH = 64 -private val CHAR_POOL: List = ('A'..'F') + ('0'..'9') - -internal fun getRandom64ByteHexString() = - (1..STRING_LENGTH) - .map { CHAR_POOL.random() } - .joinToString("") - -internal const val defaultLocalPort = 1025 -internal fun initLocal(useTLs: Boolean = false, port: Int = defaultLocalPort) = - WakuRelayRepository(useTLs, "127.0.0.1", port) \ No newline at end of file