Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9cb206c
rusty pair and approval demo
jakubuid Aug 5, 2025
3a023ea
add missing fields from proposal
jakubuid Aug 5, 2025
cc9197d
add redirect and relay
jakubuid Aug 5, 2025
08f86f1
polish pair and approve flow
jakubuid Aug 6, 2025
b70a59c
emit session request from rust client
jakubuid Aug 8, 2025
29a0240
respond session request
jakubuid Aug 8, 2025
a2b170c
Merge branch 'develop' of https://github.com/reown-com/reown-kotlin i…
jakubuid Aug 13, 2025
7244a40
add store session listener
jakubuid Aug 15, 2025
ae18d2e
improve setting a key for client id
jakubuid Aug 15, 2025
6a0ff55
more logs
jakubuid Aug 15, 2025
c31c75e
Merge branch 'develop' of https://github.com/reown-com/reown-kotlin i…
jakubuid Aug 18, 2025
6ee0560
add session delete handling
jakubuid Aug 18, 2025
899ef2b
call online when app going to foreground
jakubuid Aug 18, 2025
a9ce32d
add db migration
jakubuid Aug 18, 2025
d0eb990
Merge branch 'develop' of https://github.com/reown-com/reown-kotlin i…
jakubuid Aug 18, 2025
ea4338f
Merge branch 'sign_rust_client' of https://github.com/reown-com/reown…
jakubuid Aug 19, 2025
f2149e6
Merge pull request #169 from reown-com/rust_session_state
jakubuid Aug 19, 2025
78c70bb
Merge branch 'sign_rust_client' of https://github.com/reown-com/reown…
jakubuid Aug 19, 2025
3e068f0
Merge branch 'develop' of https://github.com/reown-com/reown-kotlin i…
jakubuid Sep 4, 2025
6b2292b
reintegrate
jakubuid Sep 4, 2025
41847ce
fix all the DI warnings
jakubuid Sep 8, 2025
4b059eb
adding storage callback
jakubuid Sep 9, 2025
6f84646
add storage callback handling
jakubuid Sep 9, 2025
455a31e
savepoint
jakubuid Sep 9, 2025
a1bd353
fix sym key parsing
jakubuid Sep 10, 2025
0f305d2
emit session settle result
jakubuid Sep 10, 2025
2e770d7
session reject
jakubuid Sep 11, 2025
4364488
Reject session request
jakubuid Sep 11, 2025
7a15464
Handle session disconnect
jakubuid Sep 11, 2025
96c740b
Send session disconnect
jakubuid Sep 11, 2025
19ba2d3
Handle session extend
jakubuid Sep 11, 2025
ded4c11
Handle extend event
jakubuid Sep 11, 2025
54614f2
session update
jakubuid Sep 12, 2025
9543964
connect from dapp to wallet sent
jakubuid Sep 15, 2025
a037ebe
dapp disconnect
jakubuid Sep 15, 2025
302214a
handle receive session response
jakubuid Sep 16, 2025
3c9ff01
handle on session update
jakubuid Sep 16, 2025
a6f5edf
handle session event
jakubuid Sep 23, 2025
22d4679
add history callbacks
jakubuid Sep 25, 2025
206d638
Merge branch 'develop' of https://github.com/reown-com/reown-kotlin i…
jakubuid Sep 25, 2025
3943680
handle json rpc history
jakubuid Sep 26, 2025
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ captures/
.idea/dictionaries
.idea/libraries
.idea/jarRepositories.xml
/.idea/codeStyles/Project.xml
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
Expand Down
35 changes: 35 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion core/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ dependencies {
debugApi(project(":foundation"))
releaseApi("com.reown:foundation:$FOUNDATION_VERSION")

//todo: remove yttrium from android-core - move all needed parts to sign sdk
// Use specific yttrium version for CI builds, default version for local builds
// Release builds use stable version, regular CI builds use CI version for e2e tests
val yttriumVersion = if (System.getenv("IS_RELEASE_BUILD") == "true") {
Expand All @@ -107,7 +108,7 @@ dependencies {
} else {
"0.9.55" // Use stable version for local builds
}
api("com.github.reown-com:yttrium:$yttriumVersion") //unspecified
api("com.github.reown-com:yttrium:unspecified") //$yttriumVersion
implementation("net.java.dev.jna:jna:5.17.0@aar")

api(libs.coroutines)
Expand Down
19 changes: 12 additions & 7 deletions core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.SharedPreferences
import com.reown.android.di.coreStorageModule
import com.reown.android.internal.common.di.AndroidCommonDITags
import com.reown.android.internal.common.di.KEY_CLIENT_ID
import com.reown.android.internal.common.di.coreAppKitModule
import com.reown.android.internal.common.di.coreAndroidNetworkModule
import com.reown.android.internal.common.di.coreCommonModule
import com.reown.android.internal.common.di.coreCryptoModule
Expand All @@ -14,7 +15,6 @@ import com.reown.android.internal.common.di.explorerModule
import com.reown.android.internal.common.di.keyServerModule
import com.reown.android.internal.common.di.pulseModule
import com.reown.android.internal.common.di.pushModule
import com.reown.android.internal.common.di.appKitModule
import com.reown.android.internal.common.explorer.ExplorerInterface
import com.reown.android.internal.common.explorer.ExplorerProtocol
import com.reown.android.internal.common.model.AppMetaData
Expand Down Expand Up @@ -78,6 +78,7 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter
) {
try {
require(relayServerUrl.isValidRelayServerUrl()) { "Check the schema and projectId parameter of the Server Url" }
//TODO: Init Sign rust client

setup(
application = application,
Expand All @@ -89,7 +90,7 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter
relay = relay,
onError = onError,
metaData = metaData,
keyServerUrl = keyServerUrl
keyServerUrl = keyServerUrl,
)
} catch (e: Exception) {
onError(Core.Model.Error(e))
Expand Down Expand Up @@ -119,7 +120,7 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter
relay = relay,
onError = onError,
metaData = metaData,
keyServerUrl = keyServerUrl
keyServerUrl = keyServerUrl,
)
} catch (e: Exception) {
onError(Core.Model.Error(e))
Expand All @@ -136,14 +137,15 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter
relay: RelayConnectionInterface?,
onError: (Core.Model.Error) -> Unit,
metaData: Core.Model.AppMetaData,
keyServerUrl: String?
keyServerUrl: String?,
) {
val packageName: String = application.packageName
val relayServerUrl = if (serverUrl.isNullOrEmpty()) "wss://relay.walletconnect.org?projectId=$projectId" else serverUrl

with(koinApp) {
androidContext(application)
modules(
// module { single(named(AndroidCommonDITags.SIGN_RUST_CLIENT)) { signClient } },
module { single(named(AndroidCommonDITags.PACKAGE_NAME)) { packageName } },
module { single { ProjectId(projectId) } },
module { single(named(AndroidCommonDITags.TELEMETRY_ENABLED)) { TelemetryEnabled(telemetryEnabled) } },
Expand All @@ -152,6 +154,9 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter
coreCryptoModule(),
)

// val (_, privateKey) = keyStore.getKeyPair()
// signClient = SignClient(projectId = projectId, key = privateKey.hexToBytes())

if (relay == null) {
Relay.initialize(connectionType) { error -> onError(Core.Model.Error(error)) }
}
Expand Down Expand Up @@ -180,9 +185,9 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter
coreJsonRpcModule(),
corePairingModule(Pairing, PairingController),
keyServerModule(keyServerUrl),
explorerModule(),
appKitModule(),
pulseModule(packageName)
// explorerModule(),
// coreAppKitModule(),
// pulseModule(packageName)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import java.net.URISyntaxException
import java.net.URLDecoder


internal object Validator {
object Validator {

private const val WC_URI_QUERY_KEY = "wc?uri="

@JvmSynthetic
internal fun validateWCUri(uri: String): WalletConnectUri? {
fun validateWCUri(uri: String): WalletConnectUri? {
val wcUri = getWcUri(uri)
if (!wcUri.startsWith("wc:")) return null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.supervisorScope


internal class DefaultConnectionLifecycle(
application: Application,
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry()
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(),
) : Lifecycle by lifecycleRegistry, ConnectionLifecycle {
private val job = SupervisorJob()
private var scope = CoroutineScope(job + Dispatchers.Default)
Expand All @@ -35,39 +35,27 @@ internal class DefaultConnectionLifecycle(
}

override fun reconnect() {
lifecycleRegistry.onNext(Lifecycle.State.Stopped.WithReason())
lifecycleRegistry.onNext(Lifecycle.State.Started)
// lifecycleRegistry.onNext(Lifecycle.State.Stopped.WithReason())
// lifecycleRegistry.onNext(Lifecycle.State.Started)
}

private inner class ActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {
var isResumed: Boolean = false
var job: Job? = null


override fun onActivityPaused(activity: Activity) {
isResumed = false

job = scope.launch {
delay(TimeUnit.SECONDS.toMillis(30))
if (!isResumed) {
lifecycleRegistry.onNext(Lifecycle.State.Stopped.WithReason(ShutdownReason(1000, "App is paused")))
job = null
_onResume.value = false
}
}
//TODO: call method from Rust Client
}

override fun onActivityResumed(activity: Activity) {
isResumed = true

if (job?.isActive == true) {
job?.cancel()
job = null
}

println("kobe: onResume")

scope.launch {
_onResume.value = true
}
// scope.launch {
// supervisorScope {
// signClient.online()
// }
// }
}

override fun onActivityStarted(activity: Activity) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class ManualConnectionLifecycle(
private val _onResume = MutableStateFlow<Boolean?>(null)
override val onResume: StateFlow<Boolean?> = _onResume.asStateFlow()

//TODO: call method from Rust Client
fun connect() {
scope.launch {
connectionMutex.withLock {
Expand All @@ -33,6 +34,7 @@ internal class ManualConnectionLifecycle(
}
}

//TODO: call method from Rust Client
fun disconnect() {
scope.launch {
connectionMutex.withLock {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ enum class AndroidCommonDITags {
ENABLE_AUTHENTICATE,
TELEMETRY_ENABLED,
PACKAGE_NAME,
SIGN_RUST_CLIENT
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.reown.android.internal.common.di

import android.os.Build
import com.reown.android.BuildConfig
import com.reown.android.internal.common.modal.AppKitApiRepository
import com.reown.android.internal.common.modal.data.network.AppKitService
Expand All @@ -11,23 +12,63 @@ import com.reown.android.internal.common.modal.domain.usecase.GetSampleWalletsUs
import com.reown.android.internal.common.modal.domain.usecase.GetSampleWalletsUseCaseInterface
import com.reown.android.internal.common.modal.domain.usecase.GetWalletsUseCase
import com.reown.android.internal.common.modal.domain.usecase.GetWalletsUseCaseInterface
import com.reown.android.internal.common.model.ProjectId
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.android.ext.koin.androidContext
import org.koin.core.qualifier.named
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.util.concurrent.TimeUnit

@JvmSynthetic
internal fun appKitModule() = module {
fun coreAppKitModule(projectId: String) = module {

factory(named(AndroidCommonDITags.USER_AGENT)) {
"""wc-2/reown-kotlin-${BuildConfig.SDK_VERSION}/android-${Build.VERSION.RELEASE}"""
}

single(named(AndroidCommonDITags.SHARED_INTERCEPTOR)) {
Interceptor { chain ->
val updatedRequest = chain.request().newBuilder()
.addHeader("User-Agent", get(named(AndroidCommonDITags.USER_AGENT)))
.build()

chain.proceed(updatedRequest)
}
}

single<Interceptor>(named(AndroidCommonDITags.LOGGING_INTERCEPTOR)) {
HttpLoggingInterceptor().apply { setLevel(HttpLoggingInterceptor.Level.BODY) }
}

single(named(AndroidCommonDITags.OK_HTTP)) {
OkHttpClient.Builder()
.addInterceptor(get<Interceptor>(named(AndroidCommonDITags.SHARED_INTERCEPTOR)))
// .authenticator((get(named(AndroidCommonDITags.AUTHENTICATOR))))
.writeTimeout(15_000L, TimeUnit.MILLISECONDS)
.readTimeout(15_000L, TimeUnit.MILLISECONDS)
.callTimeout(15_000L, TimeUnit.MILLISECONDS)
.connectTimeout(15_000L, TimeUnit.MILLISECONDS)
.apply {
if (BuildConfig.DEBUG) {
val loggingInterceptor = get<Interceptor>(named(AndroidCommonDITags.LOGGING_INTERCEPTOR))
addInterceptor(loggingInterceptor)
}
}
.retryOnConnectionFailure(true)
.build()
}

single(named(AndroidCommonDITags.WEB3MODAL_URL)) { "https://api.web3modal.com/" }

single(named(AndroidCommonDITags.APPKIT_INTERCEPTOR)) {
Interceptor { chain ->
val updatedRequest = chain.request().newBuilder()
.addHeader("x-project-id", get<ProjectId>().value)
.addHeader("x-project-id", projectId)
.addHeader("x-sdk-version", "kotlin-${BuildConfig.SDK_VERSION}")
.build()
chain.proceed(updatedRequest)
Expand Down Expand Up @@ -61,6 +102,6 @@ internal fun appKitModule() = module {

single<GetInstalledWalletsIdsUseCaseInterface> { GetInstalledWalletsIdsUseCase(appKitApiRepository = get()) }
single<GetWalletsUseCaseInterface> { GetWalletsUseCase(appKitApiRepository = get()) }
single<GetSampleWalletsUseCaseInterface> { GetSampleWalletsUseCase(context = get()) }
single<GetSampleWalletsUseCaseInterface> { GetSampleWalletsUseCase(context = androidContext()) }
single<EnableAnalyticsUseCaseInterface> { EnableAnalyticsUseCase(repository = get()) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.koin.core.scope.Scope
import org.koin.dsl.module
import com.reown.android.internal.common.scope as wcScope

//TODO: move to Sign module
fun baseStorageModule(storagePrefix: String = String.Empty, packageName: String) = module {
single<ColumnAdapter<List<String>, String>>(named(AndroidCommonDITags.COLUMN_ADAPTER_LIST)) {
object : ColumnAdapter<List<String>, String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,20 @@ private const val ANDROID_KEY_STORE = "AndroidKeyStore"
private const val SHARED_PREFS_FILE = "wc_key_store"
private const val KEY_STORE_ALIAS = "wc_keystore_key"
private const val KEY_SIZE = 256

@JvmSynthetic
fun coreCryptoModule(sharedPrefsFile: String = SHARED_PREFS_FILE, keyStoreAlias: String = KEY_STORE_ALIAS) = module {
fun coreCryptoModule(
sharedPrefsFile: String = SHARED_PREFS_FILE,
keyStoreAlias: String = KEY_STORE_ALIAS
) = module {

@Synchronized
fun Scope.createSharedPreferences(): SharedPreferences {
val keyGenParameterSpec: KeyGenParameterSpec =
KeyGenParameterSpec.Builder(keyStoreAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
KeyGenParameterSpec.Builder(
keyStoreAlias,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.setKeySize(KEY_SIZE)
Expand Down Expand Up @@ -69,7 +76,8 @@ fun coreCryptoModule(sharedPrefsFile: String = SHARED_PREFS_FILE, keyStoreAlias:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
deleteSharedPreferences(sharedPrefsFile)
} else {
getSharedPreferences(sharedPrefsFile, Context.MODE_PRIVATE).edit().clear().apply()
getSharedPreferences(sharedPrefsFile, Context.MODE_PRIVATE).edit().clear()
.apply()
val dir = File(applicationInfo.dataDir, "shared_prefs")
File(dir, "$sharedPrefsFile.xml").delete()
}
Expand Down
Loading
Loading