Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wallet connect v2 #134

Merged
merged 11 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@ plugins {

android {
namespace 'org.xmtp.android.example'
compileSdk 33
compileSdk 34

defaultConfig {
applicationId "org.xmtp.android.example"
minSdk 27
targetSdk 33
targetSdk 34
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
buildConfigField("String", "PROJECT_ID", "\"Your Application ID from https://cloud.walletconnect.com/\"")
}

buildTypes {
Expand Down Expand Up @@ -63,9 +64,18 @@ dependencies {
implementation 'androidx.activity:activity-ktx:1.6.1'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'dev.pinkroom:walletconnectkit:0.3.2'
implementation 'org.web3j:crypto:5.0.0'

// WalletConnect V2: core library + WalletConnectModal
implementation(platform("com.walletconnect:android-bom:1.19.1"))
implementation("com.walletconnect:android-core")
implementation("com.walletconnect:walletconnect-modal")

//Navigation Component
def nav_version = "2.7.5"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
Expand Down
27 changes: 25 additions & 2 deletions example/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<queries>
<package android:name="io.metamask"/>
<package android:name="com.wallet.crypto.trustapp"/>
<package android:name="io.gnosis.safe"/>
<package android:name="me.rainbow"/>
<package android:name="im.token.app"/>
<package android:name="io.zerion.android"/>
<package android:name="com.spot.spot"/>
<package android:name="fi.steakwallet.app"/>
<package android:name="vip.mytokenpocket"/>
<package android:name="com.frontierwallet"/>
<package android:name="com.bitkeep.wallet"/>
<package android:name="im.argent.contractwalletclient"/>
<package android:name="com.walletconnect.sample.wallet"/>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="wc" />
Expand All @@ -21,10 +34,12 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name=".ExampleApp"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.XMTPAndroid"
tools:targetApi="31">
tools:targetApi="31"
tools:replace="dataExtractionRules">
<activity
android:name=".MainActivity"
android:exported="true"
Expand All @@ -36,7 +51,15 @@
</activity>
<activity
android:name=".connect.ConnectWalletActivity"
android:exported="false" />
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:scheme="xmtp-example-wc"
android:host="request" />
</intent-filter>
</activity>
<activity
android:name=".conversation.ConversationDetailActivity"
android:exported="false" />
Expand Down
47 changes: 47 additions & 0 deletions example/src/main/java/org/xmtp/android/example/ExampleApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.xmtp.android.example

import android.app.Application
import com.walletconnect.android.Core
import com.walletconnect.android.CoreClient
import com.walletconnect.android.relay.ConnectionType
import com.walletconnect.wcmodal.client.Modal
import com.walletconnect.wcmodal.client.WalletConnectModal
import timber.log.Timber

const val BASE_LOG_TAG = "WC2"
class ExampleApp: Application() {

override fun onCreate() {
super.onCreate()
val connectionType = ConnectionType.AUTOMATIC
val relayUrl = "relay.walletconnect.com"
val serverUrl = "wss://$relayUrl?projectId=${BuildConfig.PROJECT_ID}"
val appMetaData = Core.Model.AppMetaData(
name = "XMTP Example",
description = "Example app using the xmtp-android SDK",
url = "https://xmtp.org",
icons = listOf("https://avatars.githubusercontent.com/u/82580170?s=48&v=4"),
redirect = "xmtp-example-wc://request"
)

CoreClient.initialize(
metaData = appMetaData,
relayServerUrl = serverUrl,
connectionType = connectionType,
application = this,
onError = {
Timber.tag("$BASE_LOG_TAG CoreClient").d(it.toString())
}
)

WalletConnectModal.initialize(
init = Modal.Params.Init(core = CoreClient),
onSuccess = {
Timber.tag("$BASE_LOG_TAG initialize").d("initialize successfully")
},
onError = { error ->
Timber.tag("$BASE_LOG_TAG initialize").d(error.toString())
}
)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.xmtp.android.example.account

import android.net.Uri
import com.walletconnect.wcmodal.client.Modal
import kotlinx.coroutines.flow.first
import org.web3j.crypto.Keys
import org.xmtp.android.example.connect.getPersonalSignBody
import org.xmtp.android.example.extension.requestMethod
import org.xmtp.android.library.SigningKey
import org.xmtp.android.library.messages.SignatureBuilder
import org.xmtp.proto.message.contents.SignatureOuterClass


data class WalletConnectV2Account(
val session: Modal.Model.ApprovedSession,
val chain: String,
private val sendSessionRequestDeepLink: (Uri) -> Unit
) :
SigningKey {
override val address: String
get() = Keys.toChecksumAddress(
session.namespaces.getValue(chain).accounts[0].substringAfterLast(
":"
)
)

override suspend fun sign(data: ByteArray): SignatureOuterClass.Signature? {
return sign(String(data))
}

override suspend fun sign(message: String): SignatureOuterClass.Signature? {
val (parentChain, chainId, account) = session.namespaces.getValue(chain).accounts[0].split(":")
val requestParams = session.namespaces.getValue(chain).methods.find { method ->
method == "personal_sign"
}?.let { method ->
Modal.Params.Request(
sessionTopic = session.topic,
method = method,
params = getPersonalSignBody(message, account),
chainId = "$parentChain:$chainId"
)
}
runCatching {
requestMethod(requestParams!!, sendSessionRequestDeepLink).first().getOrThrow()
}
.onSuccess {
return SignatureBuilder.buildFromSignatureData(it)
}
.onFailure {}

return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.xmtp.android.example.connect


data class ChainSelectionUi(
val chainName: String,
val chainNamespace: String,
val chainReference: String,
val icon: Int,
val methods: List<String>,
val events: List<String>,
var isSelected: Boolean = false,
) {
val chainId = "${chainNamespace}:${chainReference}"
}

fun Chains.toChainUiState() = ChainSelectionUi(chainName, chainNamespace, chainReference, icon, methods, events)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.xmtp.android.example.connect

import androidx.annotation.DrawableRes
import org.xmtp.android.example.R


fun getPersonalSignBody(message:String, account: String): String {
val msg = message.encodeToByteArray()
.joinToString(separator = "", prefix = "0x") { eachByte -> "%02x".format(eachByte) }
return "[\"$msg\", \"$account\"]"
}
enum class Chains(
val chainName: String,
val chainNamespace: String,
val chainReference: String,
@DrawableRes val icon: Int,
val methods: List<String>,
val events: List<String>,
) {
ETHEREUM_MAIN(
chainName = "Ethereum",
chainNamespace = Info.Eth.chain,
chainReference = "1",
icon = R.drawable.ic_ethereum,
methods = Info.Eth.defaultMethods,
events = Info.Eth.defaultEvents,
)
}

sealed class Info {
abstract val chain: String
abstract val defaultEvents: List<String>
abstract val defaultMethods: List<String>

object Eth : Info() {
override val chain = "eip155"
override val defaultEvents: List<String> = listOf("chainChanged", "accountsChanged")
override val defaultMethods: List<String> = listOf(
"eth_sendTransaction",
"personal_sign",
"eth_sign",
"eth_signTypedData"
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ import org.xmtp.android.example.databinding.ActivityConnectWalletBinding

class ConnectWalletActivity : AppCompatActivity() {

companion object {
private const val WC_URI_SCHEME = "wc://wc?uri="
}

private val viewModel: ConnectWalletViewModel by viewModels()
private lateinit var binding: ActivityConnectWalletBinding

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -33,72 +28,10 @@ class ConnectWalletActivity : AppCompatActivity() {
binding = ActivityConnectWalletBinding.inflate(layoutInflater)
setContentView(binding.root)

lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect(::ensureUiState)
}
}

binding.generateButton.setOnClickListener {
viewModel.generateWallet()
}

val isConnectWalletAvailable = isConnectAvailable()
binding.connectButton.isEnabled = isConnectWalletAvailable
binding.connectError.isVisible = !isConnectWalletAvailable
binding.connectButton.setOnClickListener {
binding.connectButton.start(viewModel.walletConnectKit, ::onConnected, ::onDisconnected)
}
}

private fun onConnected(address: String) {
viewModel.connectWallet()
}

private fun onDisconnected() {
// No-op currently.
}

private fun isConnectAvailable(): Boolean {
val wcIntent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(WC_URI_SCHEME)
}
return wcIntent.resolveActivity(packageManager) != null
}

private fun ensureUiState(uiState: ConnectWalletViewModel.ConnectUiState) {
when (uiState) {
is ConnectWalletViewModel.ConnectUiState.Error -> showError(uiState.message)
ConnectWalletViewModel.ConnectUiState.Loading -> showLoading()
is ConnectWalletViewModel.ConnectUiState.Success -> signIn(
uiState.address,
uiState.encodedKeyData
)
ConnectWalletViewModel.ConnectUiState.Unknown -> Unit
}
}

private fun signIn(address: String, encodedKey: String) {
val accountManager = AccountManager.get(this)
Account(address, resources.getString(R.string.account_type)).also { account ->
accountManager.addAccountExplicitly(account, encodedKey, null)
}
startActivity(Intent(this, MainActivity::class.java))
finish()
}

private fun showError(message: String) {
binding.progress.visibility = View.GONE
binding.generateButton.visibility = View.VISIBLE
binding.connectButton.visibility = View.VISIBLE
binding.connectError.isVisible = !isConnectAvailable()
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

private fun showLoading() {
binding.progress.visibility = View.VISIBLE
binding.generateButton.visibility = View.GONE
binding.connectButton.visibility = View.GONE
binding.connectError.visibility = View.GONE
}
}
Loading
Loading