From 9cb206c652f0a168036a04b4e8fd560833c2df58 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Tue, 5 Aug 2025 08:43:29 +0200 Subject: [PATCH 01/33] rusty pair and approval demo --- core/android/build.gradle.kts | 2 +- .../kotlin/com/reown/android/CoreProtocol.kt | 18 +- .../com/reown/android/internal/Validator.kt | 5 +- .../internal/common/di/AndroidCommonDITags.kt | 1 + .../internal/common/di/CoreCryptoModule.kt | 3 +- .../clientid/ClientIdJwtRepositoryAndroid.kt | 9 +- .../android/pairing/client/PairingProtocol.kt | 8 +- .../pairing/engine/domain/PairingEngine.kt | 182 +++++++++------ .../pairing/handler/PairingController.kt | 3 + .../handler/PairingControllerInterface.kt | 4 + .../foundation/network/BaseRelayClient.kt | 2 + .../com/reown/sign/client/SignProtocol.kt | 12 +- .../kotlin/com/reown/sign/di/CallsModule.kt | 4 +- .../reown/sign/engine/domain/SignEngine.kt | 55 +++++ .../sign/engine/model/mapper/EngineMapper.kt | 20 +- .../use_case/calls/ApproveSessionUseCase.kt | 208 ++++++++++-------- 16 files changed, 364 insertions(+), 172 deletions(-) diff --git a/core/android/build.gradle.kts b/core/android/build.gradle.kts index 04b56dcf9..315170245 100644 --- a/core/android/build.gradle.kts +++ b/core/android/build.gradle.kts @@ -104,7 +104,7 @@ dependencies { } else { "0.9.43" } - api("com.github.reown-com:yttrium:$yttriumVersion") //unspecified + api("com.github.reown-com:yttrium:unspecified") //$yttriumVersion implementation("net.java.dev.jna:jna:5.15.0@aar") api(libs.coroutines) diff --git a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt index d7be0882c..f5da63e3e 100644 --- a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt +++ b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt @@ -6,7 +6,9 @@ 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.coreAndroidNetworkModule +import com.reown.android.internal.common.storage.key_chain.KeyStore import com.reown.android.internal.common.di.coreCommonModule +import com.reown.util.hexToBytes import com.reown.android.internal.common.di.coreCryptoModule import com.reown.android.internal.common.di.coreJsonRpcModule import com.reown.android.internal.common.di.corePairingModule @@ -41,6 +43,7 @@ import org.koin.android.ext.koin.androidContext import org.koin.core.KoinApplication import org.koin.core.qualifier.named import org.koin.dsl.module +import uniffi.yttrium.SignClient class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInterface { override val Pairing: PairingInterface = PairingProtocol(koinApp) @@ -53,6 +56,8 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter override val Verify: VerifyInterface = VerifyClient(koinApp) override val Explorer: ExplorerInterface = ExplorerProtocol(koinApp) + lateinit var signClient: SignClient + init { plantTimber() } @@ -78,6 +83,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, @@ -89,7 +95,8 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter relay = relay, onError = onError, metaData = metaData, - keyServerUrl = keyServerUrl + keyServerUrl = keyServerUrl, + signClient = signClient ) } catch (e: Exception) { onError(Core.Model.Error(e)) @@ -110,6 +117,8 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter try { require(projectId.isNotEmpty()) { "Project Id cannot be empty" } + signClient = SignClient(projectId = projectId) + setup( application = application, projectId = projectId, @@ -119,7 +128,8 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter relay = relay, onError = onError, metaData = metaData, - keyServerUrl = keyServerUrl + keyServerUrl = keyServerUrl, + signClient = signClient ) } catch (e: Exception) { onError(Core.Model.Error(e)) @@ -136,7 +146,8 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter relay: RelayConnectionInterface?, onError: (Core.Model.Error) -> Unit, metaData: Core.Model.AppMetaData, - keyServerUrl: String? + keyServerUrl: String?, + signClient: SignClient ) { val packageName: String = application.packageName val relayServerUrl = if (serverUrl.isNullOrEmpty()) "wss://relay.walletconnect.org?projectId=$projectId" else serverUrl @@ -146,6 +157,7 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter modules( module { single(named(AndroidCommonDITags.PACKAGE_NAME)) { packageName } }, module { single { ProjectId(projectId) } }, + module { single(named(AndroidCommonDITags.SIGN_RUST_CLIENT)) { signClient } }, module { single(named(AndroidCommonDITags.TELEMETRY_ENABLED)) { TelemetryEnabled(telemetryEnabled) } }, coreAndroidNetworkModule(relayServerUrl, connectionType, BuildConfig.SDK_VERSION, networkClientTimeout, packageName), coreCommonModule(), diff --git a/core/android/src/main/kotlin/com/reown/android/internal/Validator.kt b/core/android/src/main/kotlin/com/reown/android/internal/Validator.kt index c2bc808df..1228855b2 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/Validator.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/Validator.kt @@ -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 diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/di/AndroidCommonDITags.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/di/AndroidCommonDITags.kt index f72106b87..392573002 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/di/AndroidCommonDITags.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/di/AndroidCommonDITags.kt @@ -43,4 +43,5 @@ enum class AndroidCommonDITags { ENABLE_AUTHENTICATE, TELEMETRY_ENABLED, PACKAGE_NAME, + SIGN_RUST_CLIENT } \ No newline at end of file diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreCryptoModule.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreCryptoModule.kt index 0de91abc7..74d004dd0 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreCryptoModule.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreCryptoModule.kt @@ -27,6 +27,7 @@ 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 { @@ -94,7 +95,7 @@ fun coreCryptoModule(sharedPrefsFile: String = SHARED_PREFS_FILE, keyStoreAlias: single { KeyChain(sharedPreferences = get()) } - single { ClientIdJwtRepositoryAndroid(keyChain = get()) } + single { ClientIdJwtRepositoryAndroid(keyChain = get(), signClient = get(named(AndroidCommonDITags.SIGN_RUST_CLIENT))) } single { BouncyCastleKeyManagementRepository(keyChain = get()) } diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt index cfdd31d10..d042bcb68 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt @@ -7,8 +7,10 @@ import com.reown.android.internal.common.storage.key_chain.KeyStore import com.reown.foundation.common.model.PrivateKey import com.reown.foundation.common.model.PublicKey import com.reown.foundation.crypto.data.repository.BaseClientIdJwtRepository +import com.reown.util.hexToBytes +import uniffi.yttrium.SignClient -internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore) : BaseClientIdJwtRepository() { +internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore, private val signClient: SignClient) : BaseClientIdJwtRepository() { override fun setKeyPair(key: String, privateKey: PrivateKey, publicKey: PublicKey) { keyChain.setKeys(CLIENT_ID_KEYPAIR_TAG, privateKey, publicKey) @@ -18,6 +20,11 @@ internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore) : Ba return if (doesKeyPairExist()) { val (privateKey, publicKey) = keyChain.getKeys(CLIENT_ID_KEYPAIR_TAG) ?: throw CannotFindKeyPairException("No key pair for given tag: $CLIENT_ID_KEYPAIR_TAG") + + + println("kobe: Setting key: $privateKey") + signClient.setKey(privateKey.hexToBytes()) + publicKey to privateKey } else { generateAndStoreClientIdKeyPair() diff --git a/core/android/src/main/kotlin/com/reown/android/pairing/client/PairingProtocol.kt b/core/android/src/main/kotlin/com/reown/android/pairing/client/PairingProtocol.kt index 1a9deebec..331500541 100644 --- a/core/android/src/main/kotlin/com/reown/android/pairing/client/PairingProtocol.kt +++ b/core/android/src/main/kotlin/com/reown/android/pairing/client/PairingProtocol.kt @@ -12,14 +12,15 @@ import com.reown.android.pairing.model.mapper.toCore import com.reown.android.pulse.domain.InsertTelemetryEventUseCase import com.reown.android.relay.RelayConnectionInterface import com.reown.foundation.util.Logger +import kotlinx.coroutines.async import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import org.koin.core.KoinApplication +import org.koin.core.qualifier.named +import uniffi.yttrium.SignClient internal class PairingProtocol(private val koinApp: KoinApplication = wcKoinApp) : PairingInterface { private lateinit var pairingEngine: PairingEngine - private val logger: Logger by lazy { koinApp.koin.get() } - private val relayClient: RelayConnectionInterface by lazy { koinApp.koin.get() } - private val insertEventUseCase: InsertTelemetryEventUseCase by lazy { koinApp.koin.get() } override fun initialize() { pairingEngine = koinApp.koin.get() @@ -68,6 +69,7 @@ internal class PairingProtocol(private val koinApp: KoinApplication = wcKoinApp) onError: (Core.Model.Error) -> Unit, ) { checkEngineInitialization() + try { pairingEngine.pair( uri = pair.uri, diff --git a/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt b/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt index 3820b4408..88225593e 100644 --- a/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt +++ b/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt @@ -6,6 +6,7 @@ import com.reown.android.internal.NO_SEQUENCE_FOR_TOPIC_MESSAGE import com.reown.android.internal.Validator import com.reown.android.internal.common.JsonRpcResponse import com.reown.android.internal.common.crypto.kmr.KeyManagementRepository +import com.reown.android.internal.common.di.AndroidCommonDITags import com.reown.android.internal.common.exception.CannotFindSequenceForTopic import com.reown.android.internal.common.exception.ExpiredPairingException import com.reown.android.internal.common.exception.ExpiredPairingURIException @@ -29,6 +30,7 @@ import com.reown.android.internal.common.model.type.RelayJsonRpcInteractorInterf import com.reown.android.internal.common.scope import com.reown.android.internal.common.storage.metadata.MetadataStorageRepositoryInterface import com.reown.android.internal.common.storage.pairing.PairingStorageRepositoryInterface +import com.reown.android.internal.common.wcKoinApp import com.reown.android.internal.utils.CoreValidator.isExpired import com.reown.android.internal.utils.currentTimeInSeconds import com.reown.android.internal.utils.dayInSeconds @@ -54,6 +56,7 @@ import com.reown.util.randomBytes import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.async import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -73,6 +76,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.withTimeout +import org.koin.core.qualifier.named +import uniffi.yttrium.SessionProposalFfi +import uniffi.yttrium.SignClient import java.util.concurrent.TimeUnit //Split into PairingProtocolEngine and PairingControllerEngine @@ -103,6 +109,10 @@ internal class PairingEngine( merge(_engineEvent, _isPairingStateFlow.map { EngineDO.PairingState(it) }) .shareIn(scope, SharingStarted.Lazily, 0) + //Rust + private val signClient: SignClient by lazy { wcKoinApp.koin.get(named(AndroidCommonDITags.SIGN_RUST_CLIENT)) } + private val _sessionProposalFlow: MutableSharedFlow = MutableSharedFlow() + val sessionProposalFlow: SharedFlow = _sessionProposalFlow.asSharedFlow() // TODO: emission of events can be missed since they are emitted potentially before there's a subscriber and the event gets missed by protocols init { @@ -152,77 +162,106 @@ internal class PairingEngine( } fun pair(uri: String, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) { - scope.launch { _checkVerifyKeyFlow.emit(Unit) } - val trace: MutableList = mutableListOf() - trace.add(Trace.Pairing.PAIRING_STARTED).also { logger.log("Pairing started") } - val walletConnectUri: WalletConnectUri = Validator.validateWCUri(uri) ?: run { - scope.launch { supervisorScope { insertTelemetryEventUseCase(Props(type = EventType.Error.MALFORMED_PAIRING_URI, properties = Properties(trace = trace))) } } - return onFailure(MalformedWalletConnectUri(MALFORMED_PAIRING_URI_MESSAGE)) - } - trace.add(Trace.Pairing.PAIRING_URI_VALIDATION_SUCCESS) - val pairing = Pairing(walletConnectUri) - val pairingTopic = pairing.topic - val symmetricKey = walletConnectUri.symKey - try { - if (walletConnectUri.expiry?.isExpired() == true) { - scope.launch { supervisorScope { insertTelemetryEventUseCase(Props(type = EventType.Error.PAIRING_URI_EXPIRED, properties = Properties(trace = trace, topic = pairingTopic.value))) } } - .also { logger.error("Pairing URI expired: $pairingTopic") } - return onFailure(ExpiredPairingURIException("Pairing URI expired: $pairingTopic")) - } - trace.add(Trace.Pairing.PAIRING_URI_NOT_EXPIRED) - if (pairingRepository.getPairingOrNullByTopic(pairingTopic) != null) { - val localPairing = pairingRepository.getPairingOrNullByTopic(pairingTopic) - trace.add(Trace.Pairing.EXISTING_PAIRING) - if (!localPairing!!.isNotExpired()) { - scope.launch { supervisorScope { insertTelemetryEventUseCase(Props(type = EventType.Error.PAIRING_EXPIRED, properties = Properties(trace = trace, topic = pairingTopic.value))) } } - .also { logger.error("Pairing expired: $pairingTopic") } - return onFailure(ExpiredPairingException("Pairing expired: ${pairingTopic.value}")) - } - trace.add(Trace.Pairing.PAIRING_NOT_EXPIRED) - scope.launch { - supervisorScope { - trace.add(Trace.Pairing.EMIT_STORED_PAIRING).also { logger.log("Emitting stored pairing: $pairingTopic") } - _storedPairingTopicFlow.emit(Pair(pairingTopic, trace)) + scope.launch { + try { + println("kobe: pair uri: $uri") + val walletConnectUri = Validator.validateWCUri(uri) ?: throw Exception() + + //TODO: store proposal only with pairing SymKey + pairingRepository.insertPairing(Pairing(walletConnectUri)) + + val proposal: SessionProposalFfi? = async { + try { + signClient.pair(uri = uri) + } catch (e: Exception) { + println("kobe: pair error 1: $e") + null } + + }.await() + + println("kobe: proposal: $proposal") + if (proposal != null) { + _sessionProposalFlow.emit(proposal) } - } else { - crypto.setKey(symmetricKey, pairingTopic.value) - pairingRepository.insertPairing(pairing) - trace.add(Trace.Pairing.STORE_NEW_PAIRING).also { logger.log("Storing a new pairing: $pairingTopic") } + + } catch (e: Exception) { + println("kobe: pair error 2: $e") + onFailure(e) } - trace.add(Trace.Pairing.SUBSCRIBING_PAIRING_TOPIC).also { logger.log("Subscribing pairing topic: $pairingTopic") } - jsonRpcInteractor.subscribe(topic = pairingTopic, - onSuccess = { - trace.add(Trace.Pairing.SUBSCRIBE_PAIRING_TOPIC_SUCCESS).also { logger.log("Subscribe pairing topic success: $pairingTopic") } - onSuccess() - }, onFailure = { error -> - scope.launch { - supervisorScope { - insertTelemetryEventUseCase(Props(type = EventType.Error.PAIRING_SUBSCRIPTION_FAILURE, properties = Properties(trace = trace, topic = pairingTopic.value))) - } - }.also { logger.error("Subscribe pairing topic error: $pairingTopic, error: $error") } - onFailure(error) - } - ) - } catch (e: Exception) { - logger.error("Subscribe pairing topic error: $pairingTopic, error: $e") - if (e is NoRelayConnectionException) - scope.launch { supervisorScope { insertTelemetryEventUseCase(Props(type = EventType.Error.NO_WSS_CONNECTION, properties = Properties(trace = trace, topic = pairingTopic.value))) } } - if (e is NoInternetConnectionException) - scope.launch { - supervisorScope { - insertTelemetryEventUseCase( - Props( - type = EventType.Error.NO_INTERNET_CONNECTION, - properties = Properties(trace = trace, topic = pairingTopic.value) - ) - ) - } - } - runCatching { crypto.removeKeys(pairingTopic.value) }.onFailure { logger.error("Remove keys error: $pairingTopic, error: $it") } - jsonRpcInteractor.unsubscribe(pairingTopic) - onFailure(e) } + +// scope.launch { _checkVerifyKeyFlow.emit(Unit) } +// val trace: MutableList = mutableListOf() +// trace.add(Trace.Pairing.PAIRING_STARTED).also { logger.log("Pairing started") } +// val walletConnectUri: WalletConnectUri = Validator.validateWCUri(uri) ?: run { +// scope.launch { supervisorScope { insertTelemetryEventUseCase(Props(type = EventType.Error.MALFORMED_PAIRING_URI, properties = Properties(trace = trace))) } } +// return onFailure(MalformedWalletConnectUri(MALFORMED_PAIRING_URI_MESSAGE)) +// } +// trace.add(Trace.Pairing.PAIRING_URI_VALIDATION_SUCCESS) +// val pairing = Pairing(walletConnectUri) +// val pairingTopic = pairing.topic +// val symmetricKey = walletConnectUri.symKey +// try { +// if (walletConnectUri.expiry?.isExpired() == true) { +// scope.launch { supervisorScope { insertTelemetryEventUseCase(Props(type = EventType.Error.PAIRING_URI_EXPIRED, properties = Properties(trace = trace, topic = pairingTopic.value))) } } +// .also { logger.error("Pairing URI expired: $pairingTopic") } +// return onFailure(ExpiredPairingURIException("Pairing URI expired: $pairingTopic")) +// } +// trace.add(Trace.Pairing.PAIRING_URI_NOT_EXPIRED) +// if (pairingRepository.getPairingOrNullByTopic(pairingTopic) != null) { +// val localPairing = pairingRepository.getPairingOrNullByTopic(pairingTopic) +// trace.add(Trace.Pairing.EXISTING_PAIRING) +// if (!localPairing!!.isNotExpired()) { +// scope.launch { supervisorScope { insertTelemetryEventUseCase(Props(type = EventType.Error.PAIRING_EXPIRED, properties = Properties(trace = trace, topic = pairingTopic.value))) } } +// .also { logger.error("Pairing expired: $pairingTopic") } +// return onFailure(ExpiredPairingException("Pairing expired: ${pairingTopic.value}")) +// } +// trace.add(Trace.Pairing.PAIRING_NOT_EXPIRED) +// scope.launch { +// supervisorScope { +// trace.add(Trace.Pairing.EMIT_STORED_PAIRING).also { logger.log("Emitting stored pairing: $pairingTopic") } +// _storedPairingTopicFlow.emit(Pair(pairingTopic, trace)) +// } +// } +// } else { +// crypto.setKey(symmetricKey, pairingTopic.value) +// pairingRepository.insertPairing(pairing) +// trace.add(Trace.Pairing.STORE_NEW_PAIRING).also { logger.log("Storing a new pairing: $pairingTopic") } +// } +// trace.add(Trace.Pairing.SUBSCRIBING_PAIRING_TOPIC).also { logger.log("Subscribing pairing topic: $pairingTopic") } +// jsonRpcInteractor.subscribe(topic = pairingTopic, +// onSuccess = { +// trace.add(Trace.Pairing.SUBSCRIBE_PAIRING_TOPIC_SUCCESS).also { logger.log("Subscribe pairing topic success: $pairingTopic") } +// onSuccess() +// }, onFailure = { error -> +// scope.launch { +// supervisorScope { +// insertTelemetryEventUseCase(Props(type = EventType.Error.PAIRING_SUBSCRIPTION_FAILURE, properties = Properties(trace = trace, topic = pairingTopic.value))) +// } +// }.also { logger.error("Subscribe pairing topic error: $pairingTopic, error: $error") } +// onFailure(error) +// } +// ) +// } catch (e: Exception) { +// logger.error("Subscribe pairing topic error: $pairingTopic, error: $e") +// if (e is NoRelayConnectionException) +// scope.launch { supervisorScope { insertTelemetryEventUseCase(Props(type = EventType.Error.NO_WSS_CONNECTION, properties = Properties(trace = trace, topic = pairingTopic.value))) } } +// if (e is NoInternetConnectionException) +// scope.launch { +// supervisorScope { +// insertTelemetryEventUseCase( +// Props( +// type = EventType.Error.NO_INTERNET_CONNECTION, +// properties = Properties(trace = trace, topic = pairingTopic.value) +// ) +// ) +// } +// } +// runCatching { crypto.removeKeys(pairingTopic.value) }.onFailure { logger.error("Remove keys error: $pairingTopic, error: $it") } +// jsonRpcInteractor.unsubscribe(pairingTopic) +// onFailure(e) +// } } @Deprecated(message = "Disconnect method has been deprecated. It will be removed soon. Pairing will disconnect automatically internally.") @@ -235,7 +274,8 @@ internal class PairingEngine( val pairingDelete = PairingRpc.PairingDelete(params = deleteParams) val irnParams = IrnParams(Tags.PAIRING_DELETE, Ttl(dayInSeconds), correlationId = pairingDelete.id) logger.log("Sending Pairing disconnect") - jsonRpcInteractor.publishJsonRpcRequest(Topic(topic), irnParams, pairingDelete, + jsonRpcInteractor.publishJsonRpcRequest( + Topic(topic), irnParams, pairingDelete, onSuccess = { scope.launch { supervisorScope { @@ -259,7 +299,8 @@ internal class PairingEngine( val pingPayload = PairingRpc.PairingPing(params = PairingParams.PingParams()) val irnParams = IrnParams(Tags.PAIRING_PING, Ttl(thirtySeconds), correlationId = pingPayload.id) - jsonRpcInteractor.publishJsonRpcRequest(Topic(topic), irnParams, pingPayload, + jsonRpcInteractor.publishJsonRpcRequest( + Topic(topic), irnParams, pingPayload, onSuccess = { onPingSuccess(pingPayload, onSuccess, topic, onFailure) }, onFailure = { error -> onFailure(error) }) } else { @@ -318,7 +359,8 @@ internal class PairingEngine( private suspend fun sendBatchSubscribeForPairings() { try { - val pairingTopics = pairingRepository.getListOfPairings().filter { pairing -> pairing.isNotExpired() }.map { pairing -> pairing.topic.value } + val pairingTopics = + pairingRepository.getListOfPairings().filter { pairing -> pairing.isNotExpired() }.map { pairing -> pairing.topic.value } jsonRpcInteractor.batchSubscribe(pairingTopics) { error -> scope.launch { internalErrorFlow.emit(SDKError(error)) } } } catch (e: Exception) { scope.launch { internalErrorFlow.emit(SDKError(e)) } diff --git a/core/android/src/main/kotlin/com/reown/android/pairing/handler/PairingController.kt b/core/android/src/main/kotlin/com/reown/android/pairing/handler/PairingController.kt index 09b34f22c..fb5b20e75 100644 --- a/core/android/src/main/kotlin/com/reown/android/pairing/handler/PairingController.kt +++ b/core/android/src/main/kotlin/com/reown/android/pairing/handler/PairingController.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.merge import org.koin.core.KoinApplication +import uniffi.yttrium.SessionProposalFfi internal class PairingController(private val koinApp: KoinApplication = wcKoinApp) : PairingControllerInterface { private lateinit var pairingEngine: PairingEngine @@ -18,6 +19,8 @@ internal class PairingController(private val koinApp: KoinApplication = wcKoinAp override val storedPairingFlow: SharedFlow>> by lazy { pairingEngine.storedPairingTopicFlow } override val checkVerifyKeyFlow: SharedFlow by lazy { pairingEngine.checkVerifyKeyFlow } + override val sessionProposalFlow: SharedFlow by lazy { pairingEngine.sessionProposalFlow } + override fun initialize() { pairingEngine = koinApp.koin.get() } diff --git a/core/android/src/main/kotlin/com/reown/android/pairing/handler/PairingControllerInterface.kt b/core/android/src/main/kotlin/com/reown/android/pairing/handler/PairingControllerInterface.kt index 42ac8802c..d61c5aedb 100644 --- a/core/android/src/main/kotlin/com/reown/android/pairing/handler/PairingControllerInterface.kt +++ b/core/android/src/main/kotlin/com/reown/android/pairing/handler/PairingControllerInterface.kt @@ -6,6 +6,7 @@ import com.reown.android.internal.common.model.SDKError import com.reown.foundation.common.model.Topic import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow +import uniffi.yttrium.SessionProposalFfi interface PairingControllerInterface { val findWrongMethodsFlow: Flow @@ -23,4 +24,7 @@ interface PairingControllerInterface { fun register(vararg method: String) fun getPairingByTopic(topic: Topic): Pairing? + + + val sessionProposalFlow: SharedFlow } \ No newline at end of file diff --git a/foundation/src/main/kotlin/com/reown/foundation/network/BaseRelayClient.kt b/foundation/src/main/kotlin/com/reown/foundation/network/BaseRelayClient.kt index 8c897db61..bbd1e41d1 100644 --- a/foundation/src/main/kotlin/com/reown/foundation/network/BaseRelayClient.kt +++ b/foundation/src/main/kotlin/com/reown/foundation/network/BaseRelayClient.kt @@ -132,6 +132,7 @@ abstract class BaseRelayClient : RelayInterface { attestation = null, correlationId = correlationId ) + println("kobe: Propose Session: $correlationId") val proposeSessionRequest = RelayDTO.ProposeSession.Request(id = id ?: generateClientToServerId(), params = proposeSessionParams) observeProposeSessionResult(proposeSessionRequest.id, onResult) relayService.proposeSessionRequest(proposeSessionRequest) @@ -184,6 +185,7 @@ abstract class BaseRelayClient : RelayInterface { correlationId = correlationId ) val approveSessionRequest = RelayDTO.ApproveSession.Request(id = id ?: generateClientToServerId(), params = approveSessionParams) + println("kobe: Approve Session: $correlationId") observeApproveSessionResult(approveSessionRequest.id, onResult) relayService.approveSessionRequest(approveSessionRequest) }, diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/client/SignProtocol.kt b/protocol/sign/src/main/kotlin/com/reown/sign/client/SignProtocol.kt index 42dc4abe4..a55507c6c 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/client/SignProtocol.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/client/SignProtocol.kt @@ -6,6 +6,7 @@ import com.reown.android.Core import com.reown.android.internal.common.di.AndroidCommonDITags import com.reown.android.internal.common.di.DatabaseConfig import com.reown.android.internal.common.model.Expiry +import com.reown.android.internal.common.model.ProjectId import com.reown.android.internal.common.model.SDKError import com.reown.android.internal.common.scope import com.reown.android.internal.common.wcKoinApp @@ -26,11 +27,13 @@ import kotlinx.coroutines.runBlocking import org.koin.core.KoinApplication import org.koin.core.qualifier.named import org.koin.dsl.module +import uniffi.yttrium.SignClient import java.util.concurrent.atomic.AtomicBoolean class SignProtocol(private val koinApp: KoinApplication = wcKoinApp) : SignInterface { private lateinit var signEngine: SignEngine private var atomicBoolean: AtomicBoolean? = null + val signClient: SignClient by lazy { wcKoinApp.koin.get(named(AndroidCommonDITags.SIGN_RUST_CLIENT)) } companion object { val instance = SignProtocol() @@ -38,6 +41,7 @@ class SignProtocol(private val koinApp: KoinApplication = wcKoinApp) : SignInter override fun initialize(init: Sign.Params.Init, onSuccess: () -> Unit, onError: (Sign.Model.Error) -> Unit) { // TODO: re-init scope + if (!::signEngine.isInitialized) { try { koinApp.modules( @@ -63,9 +67,15 @@ class SignProtocol(private val koinApp: KoinApplication = wcKoinApp) : SignInter wcKoinApp.modules(module { single(named(AndroidCommonDITags.ENABLE_AUTHENTICATE)) { delegate.onSessionAuthenticate != null } }) handleConnectionState { connectionState -> delegate.onConnectionStateChange(connectionState) } + signEngine.engineEvent.onEach { event -> when (event) { - is EngineDO.SessionProposalEvent -> delegate.onSessionProposal(event.proposal.toClientSessionProposal(), event.context.toCore()) + is EngineDO.SessionProposalEvent -> { + println("kobe: Session Proposal: ${event.proposal}") + + delegate.onSessionProposal(event.proposal.toClientSessionProposal(), event.context.toCore()) + } + is EngineDO.SessionAuthenticateEvent -> delegate.onSessionAuthenticate?.invoke( event.toClientSessionAuthenticate(), event.verifyContext.toCore() diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/di/CallsModule.kt b/protocol/sign/src/main/kotlin/com/reown/sign/di/CallsModule.kt index d53507770..596c12394 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/di/CallsModule.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/di/CallsModule.kt @@ -96,7 +96,9 @@ internal fun callsModule() = module { sessionStorageRepository = get(), verifyContextStorageRepository = get(), insertEventUseCase = get(), - logger = get(named(AndroidCommonDITags.LOGGER)) + logger = get(named(AndroidCommonDITags.LOGGER)), + signClient = get(named(AndroidCommonDITags.SIGN_RUST_CLIENT)), + pairingRepository = get() ) } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt index 743ab81a1..e53525860 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt @@ -22,12 +22,16 @@ import com.reown.android.pulse.model.properties.Props import com.reown.android.push.notifications.DecryptMessageUseCaseInterface import com.reown.android.relay.WSSConnectionState import com.reown.android.verify.model.VerifyContext +import com.reown.foundation.common.model.Topic import com.reown.foundation.util.Logger import com.reown.sign.common.model.vo.clientsync.session.params.SignParams +import com.reown.sign.common.model.vo.proposal.ProposalVO import com.reown.sign.engine.model.EngineDO +import com.reown.sign.engine.model.mapper.toEngine import com.reown.sign.engine.model.mapper.toEngineDO import com.reown.sign.engine.model.mapper.toExpiredProposal import com.reown.sign.engine.model.mapper.toExpiredSessionRequest +import com.reown.sign.engine.model.mapper.toNamespacesVOOptional import com.reown.sign.engine.model.mapper.toSessionRequest import com.reown.sign.engine.sessionRequestEventsQueue import com.reown.sign.engine.use_case.calls.ApproveSessionAuthenticateUseCaseInterface @@ -73,6 +77,7 @@ import com.reown.sign.json_rpc.model.JsonRpcMethod import com.reown.sign.storage.authenticate.AuthenticateResponseTopicRepository import com.reown.sign.storage.proposal.ProposalStorageRepository import com.reown.sign.storage.sequence.SessionStorageRepository +import com.reown.util.bytesToHex import com.reown.utils.Empty import com.reown.utils.isSequenceValid import kotlinx.coroutines.Dispatchers @@ -197,6 +202,8 @@ internal class SignEngine( emitReceivedPendingRequestsWhilePairingOnTheSameURL() sessionProposalExpiryWatcher() sessionRequestsExpiryWatcher() + + emitSessionProposal() } fun setup() { @@ -451,6 +458,54 @@ internal class SignEngine( }.launchIn(scope) } + private fun emitSessionProposal() { + pairingController.sessionProposalFlow + .onEach { proposal -> + proposalStorageRepository.insertProposal( + ProposalVO( + pairingTopic = Topic(proposal.topic), + name = "Rust Client", + description = "Testing Rust Client", + url = "", + icons = listOf(), + requiredNamespaces = emptyMap(), + optionalNamespaces = proposal.requestedNamespaces.toEngine().toNamespacesVOOptional(), + proposerPublicKey = proposal.proposerPublicKey.bytesToHex(), + relayData = "", + relayProtocol = "", + redirect = "", + properties = null, + scopedProperties = null, + expiry = null, + requestId = proposal.id.toLong() + ) + ) + + val proposalEvent = EngineDO.SessionProposalEvent( + proposal = EngineDO.SessionProposal( + pairingTopic = proposal.topic, + name = "Rust Client", + description = "Testing Rust Client", + url = "", + icons = listOf(), + requiredNamespaces = emptyMap(), + optionalNamespaces = proposal.requestedNamespaces.toEngine(), + proposerPublicKey = proposal.proposerPublicKey.bytesToHex(), + relayData = "", + relayProtocol = "", + redirect = "", + properties = null, + scopedProperties = null, + ), + context = EngineDO.VerifyContext(1, "", Validation.UNKNOWN, "", null) + ) + + println("kobe: emitSessionProposal: $proposal") + scope.launch { _engineEvent.emit(proposalEvent) } + } + .launchIn(scope) + } + private fun emitReceivedPendingRequestsWhilePairingOnTheSameURL() { pairingController.storedPairingFlow .onEach { (pairingTopic, trace) -> diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt index e7ae82927..e63ab4735 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt @@ -30,6 +30,7 @@ import com.reown.sign.engine.model.EngineDO import com.reown.sign.engine.model.ValidationError import com.reown.sign.json_rpc.model.JsonRpcMethod import com.reown.util.Empty +import uniffi.yttrium.ProposalNamespace import java.net.URI import java.text.SimpleDateFormat import java.util.Calendar @@ -93,7 +94,11 @@ internal fun ProposalVO.toSessionProposeRequest(): WCRequest = params = SignParams.SessionProposeParams( relays = listOf(RelayProtocolOptions(protocol = relayProtocol, data = relayData)), proposer = SessionProposer(proposerPublicKey, AppMetaData(name = name, description = description, url = url, icons = icons)), - requiredNamespaces = requiredNamespaces, optionalNamespaces = optionalNamespaces, properties = properties, expiryTimestamp = expiry?.seconds, scopedProperties = scopedProperties + requiredNamespaces = requiredNamespaces, + optionalNamespaces = optionalNamespaces, + properties = properties, + expiryTimestamp = expiry?.seconds, + scopedProperties = scopedProperties ), transportType = TransportType.RELAY ) @@ -364,4 +369,15 @@ internal fun EngineDO.PayloadParams.toCacaoPayload(iss: Issuer): Cacao.Payload = @JvmSynthetic internal fun EngineDO.PayloadParams.toCAIP222Message(iss: Issuer, chainName: String): String = - this.toCacaoPayload(iss).toCAIP222Message(chainName) \ No newline at end of file + this.toCacaoPayload(iss).toCAIP222Message(chainName) + + +internal fun Map.toEngine(): Map = + this.mapValues { (_, namespace) -> + EngineDO.Namespace.Proposal(namespace.chains, namespace.methods, namespace.events) + } + +internal fun Map.toYttrium(): Map = + this.mapValues { (_, namespace) -> + ProposalNamespace(namespace.chains!!, namespace.methods, namespace.events) + } \ No newline at end of file diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt index 98bd004ca..1157d0ed8 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt @@ -1,5 +1,7 @@ package com.reown.sign.engine.use_case.calls +import com.reown.android.internal.Validator +import java.net.URI import com.reown.android.internal.common.crypto.kmr.KeyManagementRepository import com.reown.android.internal.common.exception.NoInternetConnectionException import com.reown.android.internal.common.exception.NoRelayConnectionException @@ -8,6 +10,7 @@ import com.reown.android.internal.common.model.AppMetaDataType import com.reown.android.internal.common.model.type.RelayJsonRpcInteractorInterface import com.reown.android.internal.common.scope import com.reown.android.internal.common.storage.metadata.MetadataStorageRepositoryInterface +import com.reown.android.internal.common.storage.pairing.PairingStorageRepositoryInterface import com.reown.android.internal.common.storage.verify.VerifyContextStorageRepository import com.reown.android.internal.utils.ACTIVE_SESSION import com.reown.android.internal.utils.CoreValidator.isExpired @@ -29,10 +32,15 @@ import com.reown.sign.engine.model.mapper.toMapOfNamespacesVOSession import com.reown.sign.engine.model.mapper.toSessionApproveParams import com.reown.sign.engine.model.mapper.toSessionProposeRequest import com.reown.sign.engine.model.mapper.toSessionSettleParams +import com.reown.sign.engine.model.mapper.toYttrium import com.reown.sign.storage.proposal.ProposalStorageRepository import com.reown.sign.storage.sequence.SessionStorageRepository +import com.reown.util.hexToBytes +import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import uniffi.yttrium.SessionProposalFfi +import uniffi.yttrium.SignClient internal class ApproveSessionUseCase( private val jsonRpcInteractor: RelayJsonRpcInteractorInterface, @@ -43,7 +51,9 @@ internal class ApproveSessionUseCase( private val verifyContextStorageRepository: VerifyContextStorageRepository, private val selfAppMetaData: AppMetaData, private val insertEventUseCase: InsertTelemetryEventUseCase, - private val logger: Logger + private val logger: Logger, + private val signClient: SignClient, + private val pairingRepository: PairingStorageRepositoryInterface ) : ApproveSessionUseCaseInterface { override suspend fun approve( @@ -54,100 +64,124 @@ internal class ApproveSessionUseCase( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit ) = supervisorScope { - val trace: MutableList = mutableListOf() - trace.add(Trace.Session.SESSION_APPROVE_STARTED).also { logger.log(Trace.Session.SESSION_APPROVE_STARTED) } +// val trace: MutableList = mutableListOf() +// trace.add(Trace.Session.SESSION_APPROVE_STARTED).also { logger.log(Trace.Session.SESSION_APPROVE_STARTED) } val proposal = proposalStorageRepository.getProposalByKey(proposerPublicKey) - val request = proposal.toSessionProposeRequest() - val pairingTopic = proposal.pairingTopic.value - try { - proposal.expiry?.let { - if (it.isExpired()) { - insertEventUseCase(Props(type = EventType.Error.PROPOSAL_EXPIRED, properties = Properties(trace = trace, topic = pairingTopic))) - .also { logger.error("Proposal expired on approve, topic: $pairingTopic, id: ${proposal.requestId}") } - throw SessionProposalExpiredException("Session proposal expired") - } - } - trace.add(Trace.Session.PROPOSAL_NOT_EXPIRED) - SignValidator.validateSessionNamespace(sessionNamespaces.toMapOfNamespacesVOSession(), proposal.requiredNamespaces) { error -> - insertEventUseCase( - Props( - type = EventType.Error.SESSION_APPROVE_NAMESPACE_VALIDATION_FAILURE, - properties = Properties(trace = trace, topic = pairingTopic) + val pairing = pairingRepository.getPairingOrNullByTopic(proposal.pairingTopic) ?: throw Exception("No pairing") + val wcURI = Validator.validateWCUri(pairing.uri) ?: throw Exception("Malformed WC URI") + + val result = async { + try { + signClient.approve( + SessionProposalFfi( + id = proposal.requestId.toString(), + topic = proposal.pairingTopic.value, + pairingSymKey = wcURI.symKey.keyAsBytes , + proposerPublicKey = proposerPublicKey.hexToBytes(), + requestedNamespaces = sessionNamespaces.toYttrium() ) ) - .also { logger.log("Session approve failure - invalid namespaces, error: ${error.message}") } - throw InvalidNamespaceException(error.message) + } catch (e: Exception) { + println("kobe: Approve Error: $e") + onFailure(e) } - trace.add(Trace.Session.SESSION_NAMESPACE_VALIDATION_SUCCESS) - val selfPublicKey: PublicKey = crypto.generateAndStoreX25519KeyPair() - val sessionTopic = crypto.generateTopicFromKeyAgreement(selfPublicKey, PublicKey(proposerPublicKey)) - trace.add(Trace.Session.CREATE_SESSION_TOPIC) - val approvalParams = proposal.toSessionApproveParams(selfPublicKey) - - //settlement - val selfParticipant = SessionParticipant(selfPublicKey.keyAsHex, selfAppMetaData) - val sessionExpiry = ACTIVE_SESSION - val unacknowledgedSession = - SessionVO.createUnacknowledgedSession( - sessionTopic, - proposal, - selfParticipant, - sessionExpiry, - sessionNamespaces, - scopedProperties, - sessionProperties, - pairingTopic - ) - sessionStorageRepository.insertSession(unacknowledgedSession, request.id) - metadataStorageRepository.insertOrAbortMetadata(sessionTopic, selfAppMetaData, AppMetaDataType.SELF) - metadataStorageRepository.insertOrAbortMetadata(sessionTopic, proposal.appMetaData, AppMetaDataType.PEER) - trace.add(Trace.Session.STORE_SESSION) - val params = proposal.toSessionSettleParams(selfParticipant, sessionExpiry, sessionNamespaces, sessionProperties, scopedProperties) - val sessionSettle = SignRpc.SessionSettle(params = params) - trace.add(Trace.Session.PUBLISHING_SESSION_APPROVE) - jsonRpcInteractor.approveSession( - pairingTopic = proposal.pairingTopic, - sessionTopic = sessionTopic, - sessionProposalResponse = approvalParams, - settleRequest = sessionSettle, - correlationId = proposal.requestId, - onSuccess = { - onSuccess() - scope.launch { - supervisorScope { - trace.add(Trace.Session.SESSION_APPROVE_SUCCESS) - proposalStorageRepository.deleteProposal(proposerPublicKey) - verifyContextStorageRepository.delete(proposal.requestId) - } - } - }, - onFailure = { error -> - onFailure(error) - scope.launch { - supervisorScope { - insertEventUseCase( - Props( - type = EventType.Error.SESSION_APPROVE_FAILURE, - properties = Properties(trace = trace, topic = sessionTopic.value) - ) - ).also { logger.error("Session approve failure, topic: ${sessionTopic.value}") } - } - } - } - ) - } catch (e: Exception) { - if (e is NoRelayConnectionException) { - insertEventUseCase(Props(type = EventType.Error.NO_WSS_CONNECTION, properties = Properties(trace = trace, topic = pairingTopic))) - } - if (e is NoInternetConnectionException) { - insertEventUseCase(Props(type = EventType.Error.NO_INTERNET_CONNECTION, properties = Properties(trace = trace, topic = pairingTopic))) - } - onFailure(e) - } + }.await() + println("kobe: Result: $result") + onSuccess() } } +// val request = proposal.toSessionProposeRequest() +// val pairingTopic = proposal.pairingTopic.value +// try { +// proposal.expiry?.let { +// if (it.isExpired()) { +// insertEventUseCase(Props(type = EventType.Error.PROPOSAL_EXPIRED, properties = Properties(trace = trace, topic = pairingTopic))) +// .also { logger.error("Proposal expired on approve, topic: $pairingTopic, id: ${proposal.requestId}") } +// throw SessionProposalExpiredException("Session proposal expired") +// } +// } +// trace.add(Trace.Session.PROPOSAL_NOT_EXPIRED) +// SignValidator.validateSessionNamespace(sessionNamespaces.toMapOfNamespacesVOSession(), proposal.requiredNamespaces) { error -> +// insertEventUseCase( +// Props( +// type = EventType.Error.SESSION_APPROVE_NAMESPACE_VALIDATION_FAILURE, +// properties = Properties(trace = trace, topic = pairingTopic) +// ) +// ) +// .also { logger.log("Session approve failure - invalid namespaces, error: ${error.message}") } +// throw InvalidNamespaceException(error.message) +// } +// trace.add(Trace.Session.SESSION_NAMESPACE_VALIDATION_SUCCESS) +// val selfPublicKey: PublicKey = crypto.generateAndStoreX25519KeyPair() +// val sessionTopic = crypto.generateTopicFromKeyAgreement(selfPublicKey, PublicKey(proposerPublicKey)) +// trace.add(Trace.Session.CREATE_SESSION_TOPIC) +// val approvalParams = proposal.toSessionApproveParams(selfPublicKey) +// +// //settlement +// val selfParticipant = SessionParticipant(selfPublicKey.keyAsHex, selfAppMetaData) +// val sessionExpiry = ACTIVE_SESSION +// val unacknowledgedSession = +// SessionVO.createUnacknowledgedSession( +// sessionTopic, +// proposal, +// selfParticipant, +// sessionExpiry, +// sessionNamespaces, +// scopedProperties, +// sessionProperties, +// pairingTopic +// ) +// +// sessionStorageRepository.insertSession(unacknowledgedSession, request.id) +// metadataStorageRepository.insertOrAbortMetadata(sessionTopic, selfAppMetaData, AppMetaDataType.SELF) +// metadataStorageRepository.insertOrAbortMetadata(sessionTopic, proposal.appMetaData, AppMetaDataType.PEER) +// trace.add(Trace.Session.STORE_SESSION) +// val params = proposal.toSessionSettleParams(selfParticipant, sessionExpiry, sessionNamespaces, sessionProperties, scopedProperties) +// val sessionSettle = SignRpc.SessionSettle(params = params) +// trace.add(Trace.Session.PUBLISHING_SESSION_APPROVE) +// jsonRpcInteractor.approveSession( +// pairingTopic = proposal.pairingTopic, +// sessionTopic = sessionTopic, +// sessionProposalResponse = approvalParams, +// settleRequest = sessionSettle, +// correlationId = proposal.requestId, +// onSuccess = { +// onSuccess() +// scope.launch { +// supervisorScope { +// trace.add(Trace.Session.SESSION_APPROVE_SUCCESS) +// proposalStorageRepository.deleteProposal(proposerPublicKey) +// verifyContextStorageRepository.delete(proposal.requestId) +// } +// } +// }, +// onFailure = { error -> +// onFailure(error) +// scope.launch { +// supervisorScope { +// insertEventUseCase( +// Props( +// type = EventType.Error.SESSION_APPROVE_FAILURE, +// properties = Properties(trace = trace, topic = sessionTopic.value) +// ) +// ).also { logger.error("Session approve failure, topic: ${sessionTopic.value}") } +// } +// } +// } +// ) +// } catch (e: Exception) { +// if (e is NoRelayConnectionException) { +// insertEventUseCase(Props(type = EventType.Error.NO_WSS_CONNECTION, properties = Properties(trace = trace, topic = pairingTopic))) +// } +// if (e is NoInternetConnectionException) { +// insertEventUseCase(Props(type = EventType.Error.NO_INTERNET_CONNECTION, properties = Properties(trace = trace, topic = pairingTopic))) +// } +// onFailure(e) +// } +// } +//} internal interface ApproveSessionUseCaseInterface { suspend fun approve( From 3a023eafae793b76aaefcb34625370ac20b3cabf Mon Sep 17 00:00:00 2001 From: jakubuid Date: Tue, 5 Aug 2025 16:29:18 +0200 Subject: [PATCH 02/33] add missing fields from proposal --- .../kotlin/com/reown/android/CoreProtocol.kt | 12 ++++++- .../clientid/ClientIdJwtRepositoryAndroid.kt | 11 ++++-- .../reown/sign/engine/domain/SignEngine.kt | 36 ++++++++++--------- .../sign/engine/model/mapper/EngineMapper.kt | 2 +- .../use_case/calls/ApproveSessionUseCase.kt | 12 +++++-- 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt index f5da63e3e..941da9992 100644 --- a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt +++ b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt @@ -2,6 +2,7 @@ package com.reown.android import android.app.Application import android.content.SharedPreferences +import android.util.Log import com.reown.android.di.coreStorageModule import com.reown.android.internal.common.di.AndroidCommonDITags import com.reown.android.internal.common.di.KEY_CLIENT_ID @@ -43,6 +44,7 @@ import org.koin.android.ext.koin.androidContext import org.koin.core.KoinApplication import org.koin.core.qualifier.named import org.koin.dsl.module +import uniffi.yttrium.Logger import uniffi.yttrium.SignClient class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInterface { @@ -103,6 +105,12 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter } } + class AndroidLogger: Logger { + override fun log(message: String) { + println("kobe: Message from Rust: $message") + } + } + override fun initialize( application: Application, projectId: String, @@ -117,7 +125,9 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter try { require(projectId.isNotEmpty()) { "Project Id cannot be empty" } - signClient = SignClient(projectId = projectId) + println("kobe: Kotlin Rust Init") + signClient = SignClient(projectId = projectId, logger = AndroidLogger()) + setup( application = application, diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt index d042bcb68..38ca2bb0f 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt @@ -3,11 +3,14 @@ package com.reown.android.internal.common.jwt.clientid import com.reown.android.internal.common.exception.CannotFindKeyPairException +import com.reown.android.internal.common.scope import com.reown.android.internal.common.storage.key_chain.KeyStore import com.reown.foundation.common.model.PrivateKey import com.reown.foundation.common.model.PublicKey import com.reown.foundation.crypto.data.repository.BaseClientIdJwtRepository import com.reown.util.hexToBytes +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import uniffi.yttrium.SignClient internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore, private val signClient: SignClient) : BaseClientIdJwtRepository() { @@ -21,11 +24,13 @@ internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore, priv val (privateKey, publicKey) = keyChain.getKeys(CLIENT_ID_KEYPAIR_TAG) ?: throw CannotFindKeyPairException("No key pair for given tag: $CLIENT_ID_KEYPAIR_TAG") + runBlocking { + println("kobe: Setting key: $privateKey") + signClient.setKey(privateKey.hexToBytes()) - println("kobe: Setting key: $privateKey") - signClient.setKey(privateKey.hexToBytes()) + publicKey to privateKey + } - publicKey to privateKey } else { generateAndStoreClientIdKeyPair() } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt index e53525860..cce5111ec 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt @@ -5,6 +5,7 @@ package com.reown.sign.engine.domain import com.reown.android.internal.common.crypto.kmr.KeyManagementRepository import com.reown.android.internal.common.json_rpc.domain.link_mode.LinkModeJsonRpcInteractorInterface import com.reown.android.internal.common.model.AppMetaDataType +import com.reown.android.internal.common.model.Expiry import com.reown.android.internal.common.model.SDKError import com.reown.android.internal.common.model.Validation import com.reown.android.internal.common.model.type.EngineEvent @@ -94,6 +95,7 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import java.net.URI internal class SignEngine( private val jsonRpcInteractor: RelayJsonRpcInteractorInterface, @@ -464,19 +466,19 @@ internal class SignEngine( proposalStorageRepository.insertProposal( ProposalVO( pairingTopic = Topic(proposal.topic), - name = "Rust Client", - description = "Testing Rust Client", - url = "", - icons = listOf(), - requiredNamespaces = emptyMap(), - optionalNamespaces = proposal.requestedNamespaces.toEngine().toNamespacesVOOptional(), + name = proposal.metadata.name, + description = proposal.metadata.description, + url = proposal.metadata.url, + icons = proposal.metadata.icons, + requiredNamespaces = proposal.requiredNamespaces.toEngine().toNamespacesVOOptional(), + optionalNamespaces = proposal.optionalNamespaces?.toEngine()?.toNamespacesVOOptional() ?: emptyMap(), proposerPublicKey = proposal.proposerPublicKey.bytesToHex(), relayData = "", relayProtocol = "", redirect = "", - properties = null, - scopedProperties = null, - expiry = null, + properties =proposal.scopedProperties, + scopedProperties = proposal.scopedProperties, + expiry = if (proposal.expiryTimestamp != null) Expiry(proposal.expiryTimestamp!!.toLong()) else null, requestId = proposal.id.toLong() ) ) @@ -484,18 +486,18 @@ internal class SignEngine( val proposalEvent = EngineDO.SessionProposalEvent( proposal = EngineDO.SessionProposal( pairingTopic = proposal.topic, - name = "Rust Client", - description = "Testing Rust Client", - url = "", - icons = listOf(), - requiredNamespaces = emptyMap(), - optionalNamespaces = proposal.requestedNamespaces.toEngine(), + name = proposal.metadata.name, + description = proposal.metadata.description, + url = proposal.metadata.url, + icons = proposal.metadata.icons.map { URI(it) }, + requiredNamespaces = proposal.optionalNamespaces?.toEngine() ?: emptyMap(), + optionalNamespaces = proposal.optionalNamespaces?.toEngine() ?: emptyMap(), proposerPublicKey = proposal.proposerPublicKey.bytesToHex(), relayData = "", relayProtocol = "", redirect = "", - properties = null, - scopedProperties = null, + properties = proposal.sessionProperties, + scopedProperties = proposal.scopedProperties, ), context = EngineDO.VerifyContext(1, "", Validation.UNKNOWN, "", null) ) diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt index e63ab4735..aaa4744da 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt @@ -377,7 +377,7 @@ internal fun Map.toEngine(): Map.toYttrium(): Map = +internal fun Map.toYttrium(): Map = this.mapValues { (_, namespace) -> ProposalNamespace(namespace.chains!!, namespace.methods, namespace.events) } \ No newline at end of file diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt index 1157d0ed8..fbf89594b 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt @@ -39,6 +39,7 @@ import com.reown.util.hexToBytes import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import uniffi.yttrium.Metadata import uniffi.yttrium.SessionProposalFfi import uniffi.yttrium.SignClient @@ -77,9 +78,16 @@ internal class ApproveSessionUseCase( SessionProposalFfi( id = proposal.requestId.toString(), topic = proposal.pairingTopic.value, - pairingSymKey = wcURI.symKey.keyAsBytes , + pairingSymKey = wcURI.symKey.keyAsBytes, proposerPublicKey = proposerPublicKey.hexToBytes(), - requestedNamespaces = sessionNamespaces.toYttrium() + requiredNamespaces = proposal.requiredNamespaces.toYttrium(), + optionalNamespaces = proposal.optionalNamespaces.toYttrium(), + sessionProperties = sessionProperties, + scopedProperties = scopedProperties, + expiryTimestamp = proposal.expiry?.seconds?.toULong(), + metadata = uniffi.yttrium.Metadata( + name = proposal.name, description = proposal.description, icons = proposal.icons, url = proposal.url + ) ) ) } catch (e: Exception) { From cc9197daa357f9b8bcf1330e2d87eb18ad92a5fd Mon Sep 17 00:00:00 2001 From: jakubuid Date: Tue, 5 Aug 2025 17:26:15 +0200 Subject: [PATCH 03/33] add redirect and relay --- .../pairing/engine/domain/PairingEngine.kt | 2 +- .../com/reown/sign/engine/domain/SignEngine.kt | 10 +++++----- .../use_case/calls/ApproveSessionUseCase.kt | 15 ++++++++++++++- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt b/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt index 88225593e..8ea58d7a0 100644 --- a/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt +++ b/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt @@ -172,7 +172,7 @@ internal class PairingEngine( val proposal: SessionProposalFfi? = async { try { - signClient.pair(uri = uri) + signClient.pair(uri = walletConnectUri.toAbsoluteString()) } catch (e: Exception) { println("kobe: pair error 1: $e") null diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt index cce5111ec..a499cb432 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt @@ -474,9 +474,9 @@ internal class SignEngine( optionalNamespaces = proposal.optionalNamespaces?.toEngine()?.toNamespacesVOOptional() ?: emptyMap(), proposerPublicKey = proposal.proposerPublicKey.bytesToHex(), relayData = "", - relayProtocol = "", - redirect = "", - properties =proposal.scopedProperties, + relayProtocol = proposal.relays[0].protocol, + redirect = proposal.metadata.redirect?.native ?: "", + properties = proposal.scopedProperties, scopedProperties = proposal.scopedProperties, expiry = if (proposal.expiryTimestamp != null) Expiry(proposal.expiryTimestamp!!.toLong()) else null, requestId = proposal.id.toLong() @@ -494,8 +494,8 @@ internal class SignEngine( optionalNamespaces = proposal.optionalNamespaces?.toEngine() ?: emptyMap(), proposerPublicKey = proposal.proposerPublicKey.bytesToHex(), relayData = "", - relayProtocol = "", - redirect = "", + relayProtocol = proposal.relays[0].protocol, + redirect = proposal.metadata.redirect?.native ?: "", properties = proposal.sessionProperties, scopedProperties = proposal.scopedProperties, ), diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt index fbf89594b..590b181f4 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt @@ -7,6 +7,7 @@ import com.reown.android.internal.common.exception.NoInternetConnectionException import com.reown.android.internal.common.exception.NoRelayConnectionException import com.reown.android.internal.common.model.AppMetaData import com.reown.android.internal.common.model.AppMetaDataType +import com.reown.android.internal.common.model.RelayProtocolOptions import com.reown.android.internal.common.model.type.RelayJsonRpcInteractorInterface import com.reown.android.internal.common.scope import com.reown.android.internal.common.storage.metadata.MetadataStorageRepositoryInterface @@ -40,6 +41,8 @@ import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import uniffi.yttrium.Metadata +import uniffi.yttrium.Redirect +import uniffi.yttrium.Relay import uniffi.yttrium.SessionProposalFfi import uniffi.yttrium.SignClient @@ -85,8 +88,18 @@ internal class ApproveSessionUseCase( sessionProperties = sessionProperties, scopedProperties = scopedProperties, expiryTimestamp = proposal.expiry?.seconds?.toULong(), + relays = listOf(Relay(proposal.relayProtocol)), metadata = uniffi.yttrium.Metadata( - name = proposal.name, description = proposal.description, icons = proposal.icons, url = proposal.url + name = proposal.name, + verifyUrl = null, + description = proposal.description, + icons = proposal.icons, + url = proposal.url, + redirect = Redirect( + native = proposal.appMetaData.redirect?.native, + universal = proposal.appMetaData.redirect?.universal ?: "", + linkMode = proposal.appMetaData.redirect?.linkMode ?: false + ) ) ) ) From 08f86f165fa1c8d03334af229fb6f4c562516344 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Wed, 6 Aug 2025 12:04:09 +0200 Subject: [PATCH 04/33] polish pair and approve flow --- .../kotlin/com/reown/android/CoreProtocol.kt | 25 +++--- .../pairing/engine/domain/PairingEngine.kt | 12 +-- .../common/model/vo/proposal/ProposalVO.kt | 3 +- .../reown/sign/engine/domain/SignEngine.kt | 52 +++--------- .../sign/engine/model/mapper/EngineMapper.kt | 79 +++++++++++++++++- .../use_case/calls/ApproveSessionUseCase.kt | 61 ++------------ .../proposal/ProposalStorageRepository.kt | 7 +- .../storage/data/dao/proposal/ProposalDao.sq | 13 +-- .../sign/src/main/sqldelight/databases/14.db | Bin 0 -> 61440 bytes .../src/main/sqldelight/migrations/13.sqm | 5 ++ 10 files changed, 125 insertions(+), 132 deletions(-) create mode 100644 protocol/sign/src/main/sqldelight/databases/14.db create mode 100644 protocol/sign/src/main/sqldelight/migrations/13.sqm diff --git a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt index 941da9992..92980ca9f 100644 --- a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt +++ b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt @@ -2,14 +2,12 @@ package com.reown.android import android.app.Application import android.content.SharedPreferences -import android.util.Log 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.appKitModule import com.reown.android.internal.common.di.coreAndroidNetworkModule -import com.reown.android.internal.common.storage.key_chain.KeyStore import com.reown.android.internal.common.di.coreCommonModule -import com.reown.util.hexToBytes import com.reown.android.internal.common.di.coreCryptoModule import com.reown.android.internal.common.di.coreJsonRpcModule import com.reown.android.internal.common.di.corePairingModule @@ -17,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 @@ -46,6 +43,13 @@ import org.koin.core.qualifier.named import org.koin.dsl.module import uniffi.yttrium.Logger import uniffi.yttrium.SignClient +import uniffi.yttrium.registerLogger + +class AndroidLogger: Logger { + override fun log(message: String) { + println("jordan: Message from Rust: $message") + } +} class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInterface { override val Pairing: PairingInterface = PairingProtocol(koinApp) @@ -105,12 +109,6 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter } } - class AndroidLogger: Logger { - override fun log(message: String) { - println("kobe: Message from Rust: $message") - } - } - override fun initialize( application: Application, projectId: String, @@ -124,11 +122,8 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter ) { try { require(projectId.isNotEmpty()) { "Project Id cannot be empty" } - - println("kobe: Kotlin Rust Init") - signClient = SignClient(projectId = projectId, logger = AndroidLogger()) - - + registerLogger(AndroidLogger()) + signClient = SignClient(projectId = projectId) setup( application = application, projectId = projectId, diff --git a/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt b/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt index 8ea58d7a0..eb9efbafc 100644 --- a/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt +++ b/core/android/src/main/kotlin/com/reown/android/pairing/engine/domain/PairingEngine.kt @@ -164,29 +164,23 @@ internal class PairingEngine( fun pair(uri: String, onSuccess: () -> Unit, onFailure: (Throwable) -> Unit) { scope.launch { try { - println("kobe: pair uri: $uri") val walletConnectUri = Validator.validateWCUri(uri) ?: throw Exception() - - //TODO: store proposal only with pairing SymKey - pairingRepository.insertPairing(Pairing(walletConnectUri)) - + //TODO: check if proposal already stored to safe latency? val proposal: SessionProposalFfi? = async { try { signClient.pair(uri = walletConnectUri.toAbsoluteString()) } catch (e: Exception) { - println("kobe: pair error 1: $e") + println("kobe: Pair error 1: $e") null } - }.await() - println("kobe: proposal: $proposal") if (proposal != null) { _sessionProposalFlow.emit(proposal) } } catch (e: Exception) { - println("kobe: pair error 2: $e") + println("kobe: Pair error 2: $e") onFailure(e) } } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/common/model/vo/proposal/ProposalVO.kt b/protocol/sign/src/main/kotlin/com/reown/sign/common/model/vo/proposal/ProposalVO.kt index e639767c9..c0cfa7735 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/common/model/vo/proposal/ProposalVO.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/common/model/vo/proposal/ProposalVO.kt @@ -21,7 +21,8 @@ internal data class ProposalVO( val proposerPublicKey: String, val relayProtocol: String, val relayData: String?, - val expiry: Expiry? + val expiry: Expiry?, + val pairingSymKey: String ) { val appMetaData: AppMetaData get() = AppMetaData(name = name, description = description, url = url, icons = icons, redirect = Redirect(native = redirect)) diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt index a499cb432..ede322c54 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt @@ -34,6 +34,8 @@ import com.reown.sign.engine.model.mapper.toExpiredProposal import com.reown.sign.engine.model.mapper.toExpiredSessionRequest import com.reown.sign.engine.model.mapper.toNamespacesVOOptional import com.reown.sign.engine.model.mapper.toSessionRequest +import com.reown.sign.engine.model.mapper.toVO +import com.reown.sign.engine.model.mapper.toYttrium import com.reown.sign.engine.sessionRequestEventsQueue import com.reown.sign.engine.use_case.calls.ApproveSessionAuthenticateUseCaseInterface import com.reown.sign.engine.use_case.calls.ApproveSessionUseCaseInterface @@ -91,6 +93,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch @@ -462,48 +465,17 @@ internal class SignEngine( private fun emitSessionProposal() { pairingController.sessionProposalFlow + .map { it.toVO() } + .onEach { proposalStorageRepository.insertProposal(it) } .onEach { proposal -> - proposalStorageRepository.insertProposal( - ProposalVO( - pairingTopic = Topic(proposal.topic), - name = proposal.metadata.name, - description = proposal.metadata.description, - url = proposal.metadata.url, - icons = proposal.metadata.icons, - requiredNamespaces = proposal.requiredNamespaces.toEngine().toNamespacesVOOptional(), - optionalNamespaces = proposal.optionalNamespaces?.toEngine()?.toNamespacesVOOptional() ?: emptyMap(), - proposerPublicKey = proposal.proposerPublicKey.bytesToHex(), - relayData = "", - relayProtocol = proposal.relays[0].protocol, - redirect = proposal.metadata.redirect?.native ?: "", - properties = proposal.scopedProperties, - scopedProperties = proposal.scopedProperties, - expiry = if (proposal.expiryTimestamp != null) Expiry(proposal.expiryTimestamp!!.toLong()) else null, - requestId = proposal.id.toLong() + scope.launch { + _engineEvent.emit( + EngineDO.SessionProposalEvent( + proposal = proposal.toEngineDO(), + context = EngineDO.VerifyContext(1, "", Validation.UNKNOWN, "", null) + ) ) - ) - - val proposalEvent = EngineDO.SessionProposalEvent( - proposal = EngineDO.SessionProposal( - pairingTopic = proposal.topic, - name = proposal.metadata.name, - description = proposal.metadata.description, - url = proposal.metadata.url, - icons = proposal.metadata.icons.map { URI(it) }, - requiredNamespaces = proposal.optionalNamespaces?.toEngine() ?: emptyMap(), - optionalNamespaces = proposal.optionalNamespaces?.toEngine() ?: emptyMap(), - proposerPublicKey = proposal.proposerPublicKey.bytesToHex(), - relayData = "", - relayProtocol = proposal.relays[0].protocol, - redirect = proposal.metadata.redirect?.native ?: "", - properties = proposal.sessionProperties, - scopedProperties = proposal.scopedProperties, - ), - context = EngineDO.VerifyContext(1, "", Validation.UNKNOWN, "", null) - ) - - println("kobe: emitSessionProposal: $proposal") - scope.launch { _engineEvent.emit(proposalEvent) } + } } .launchIn(scope) } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt index aaa4744da..0741d65a3 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt @@ -30,7 +30,14 @@ import com.reown.sign.engine.model.EngineDO import com.reown.sign.engine.model.ValidationError import com.reown.sign.json_rpc.model.JsonRpcMethod import com.reown.util.Empty +import com.reown.util.bytesToHex +import com.reown.util.hexToBytes +import uniffi.yttrium.Metadata import uniffi.yttrium.ProposalNamespace +import uniffi.yttrium.Redirect +import uniffi.yttrium.Relay +import uniffi.yttrium.SessionProposalFfi +import uniffi.yttrium.SettleNamespace import java.net.URI import java.text.SimpleDateFormat import java.util.Calendar @@ -82,7 +89,8 @@ internal fun SignParams.SessionProposeParams.toVO(topic: Topic, requestId: Long) relayProtocol = relays.first().protocol, relayData = relays.first().data, expiry = if (expiryTimestamp != null) Expiry(expiryTimestamp) else null, - scopedProperties = scopedProperties + scopedProperties = scopedProperties, + pairingSymKey = "" ) @JvmSynthetic @@ -377,7 +385,72 @@ internal fun Map.toEngine(): Map.toYttrium(): Map = +internal fun Map.toProposalYttrium(): Map = this.mapValues { (_, namespace) -> ProposalNamespace(namespace.chains!!, namespace.methods, namespace.events) - } \ No newline at end of file + } + +internal fun Map.toSettleYttrium(): Map = + this.mapValues { (_, namespace) -> + SettleNamespace(accounts = namespace.accounts, chains = namespace.chains!!, methods = namespace.methods, events = namespace.events) + } + +internal fun AppMetaData.toYttrium(): Metadata = + Metadata( + name = name, + verifyUrl = null, + description = description, + icons = icons, + url = url, + redirect = Redirect( + native = redirect?.native, + universal = redirect?.universal ?: "", + linkMode = redirect?.linkMode ?: false + ) + ) + +internal fun SessionProposalFfi.toVO(): ProposalVO = + ProposalVO( + pairingTopic = Topic(topic), + name = metadata.name, + description = metadata.description, + url = metadata.url, + icons = metadata.icons, + requiredNamespaces = requiredNamespaces.toEngine().toNamespacesVOOptional(), + optionalNamespaces = optionalNamespaces?.toEngine()?.toNamespacesVOOptional() ?: emptyMap(), + proposerPublicKey = proposerPublicKey.bytesToHex(), + relayData = "", + relayProtocol = relays[0].protocol, + redirect = metadata.redirect?.native ?: "", + properties = scopedProperties, + scopedProperties = scopedProperties, + expiry = if (expiryTimestamp != null) Expiry(expiryTimestamp!!.toLong()) else null, + requestId = id.toLong(), + pairingSymKey = pairingSymKey.bytesToHex() + ) + +internal fun ProposalVO.toProposalFfi(): SessionProposalFfi = + SessionProposalFfi( + id = requestId.toString(), + topic = pairingTopic.value, + pairingSymKey = pairingSymKey.hexToBytes(), + proposerPublicKey = proposerPublicKey.hexToBytes(), + requiredNamespaces = requiredNamespaces.toProposalYttrium(), + optionalNamespaces = optionalNamespaces.toProposalYttrium(), + sessionProperties = properties, + scopedProperties = scopedProperties, + expiryTimestamp = expiry?.seconds?.toULong(), + relays = listOf(Relay(relayProtocol)), + metadata = uniffi.yttrium.Metadata( + name = name, + verifyUrl = null, + description = description, + icons = icons, + url = url, + redirect = Redirect( + native = appMetaData.redirect?.native, + universal = appMetaData.redirect?.universal ?: "", + linkMode = appMetaData.redirect?.linkMode ?: false + ) + ) + ) \ No newline at end of file diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt index 590b181f4..e67598bcf 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt @@ -1,46 +1,24 @@ package com.reown.sign.engine.use_case.calls import com.reown.android.internal.Validator -import java.net.URI import com.reown.android.internal.common.crypto.kmr.KeyManagementRepository -import com.reown.android.internal.common.exception.NoInternetConnectionException -import com.reown.android.internal.common.exception.NoRelayConnectionException import com.reown.android.internal.common.model.AppMetaData -import com.reown.android.internal.common.model.AppMetaDataType -import com.reown.android.internal.common.model.RelayProtocolOptions import com.reown.android.internal.common.model.type.RelayJsonRpcInteractorInterface -import com.reown.android.internal.common.scope import com.reown.android.internal.common.storage.metadata.MetadataStorageRepositoryInterface import com.reown.android.internal.common.storage.pairing.PairingStorageRepositoryInterface import com.reown.android.internal.common.storage.verify.VerifyContextStorageRepository -import com.reown.android.internal.utils.ACTIVE_SESSION -import com.reown.android.internal.utils.CoreValidator.isExpired import com.reown.android.pulse.domain.InsertTelemetryEventUseCase -import com.reown.android.pulse.model.EventType -import com.reown.android.pulse.model.Trace -import com.reown.android.pulse.model.properties.Properties -import com.reown.android.pulse.model.properties.Props -import com.reown.foundation.common.model.PublicKey import com.reown.foundation.util.Logger -import com.reown.sign.common.exceptions.InvalidNamespaceException -import com.reown.sign.common.exceptions.SessionProposalExpiredException -import com.reown.sign.common.model.vo.clientsync.common.SessionParticipant -import com.reown.sign.common.model.vo.clientsync.session.SignRpc -import com.reown.sign.common.model.vo.sequence.SessionVO -import com.reown.sign.common.validator.SignValidator import com.reown.sign.engine.model.EngineDO -import com.reown.sign.engine.model.mapper.toMapOfNamespacesVOSession -import com.reown.sign.engine.model.mapper.toSessionApproveParams -import com.reown.sign.engine.model.mapper.toSessionProposeRequest -import com.reown.sign.engine.model.mapper.toSessionSettleParams +import com.reown.sign.engine.model.mapper.toProposalFfi +import com.reown.sign.engine.model.mapper.toProposalYttrium +import com.reown.sign.engine.model.mapper.toSettleYttrium import com.reown.sign.engine.model.mapper.toYttrium import com.reown.sign.storage.proposal.ProposalStorageRepository import com.reown.sign.storage.sequence.SessionStorageRepository import com.reown.util.hexToBytes import kotlinx.coroutines.async -import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -import uniffi.yttrium.Metadata import uniffi.yttrium.Redirect import uniffi.yttrium.Relay import uniffi.yttrium.SessionProposalFfi @@ -70,46 +48,17 @@ internal class ApproveSessionUseCase( ) = supervisorScope { // val trace: MutableList = mutableListOf() // trace.add(Trace.Session.SESSION_APPROVE_STARTED).also { logger.log(Trace.Session.SESSION_APPROVE_STARTED) } - val proposal = proposalStorageRepository.getProposalByKey(proposerPublicKey) - val pairing = pairingRepository.getPairingOrNullByTopic(proposal.pairingTopic) ?: throw Exception("No pairing") - val wcURI = Validator.validateWCUri(pairing.uri) ?: throw Exception("Malformed WC URI") - val result = async { try { - signClient.approve( - SessionProposalFfi( - id = proposal.requestId.toString(), - topic = proposal.pairingTopic.value, - pairingSymKey = wcURI.symKey.keyAsBytes, - proposerPublicKey = proposerPublicKey.hexToBytes(), - requiredNamespaces = proposal.requiredNamespaces.toYttrium(), - optionalNamespaces = proposal.optionalNamespaces.toYttrium(), - sessionProperties = sessionProperties, - scopedProperties = scopedProperties, - expiryTimestamp = proposal.expiry?.seconds?.toULong(), - relays = listOf(Relay(proposal.relayProtocol)), - metadata = uniffi.yttrium.Metadata( - name = proposal.name, - verifyUrl = null, - description = proposal.description, - icons = proposal.icons, - url = proposal.url, - redirect = Redirect( - native = proposal.appMetaData.redirect?.native, - universal = proposal.appMetaData.redirect?.universal ?: "", - linkMode = proposal.appMetaData.redirect?.linkMode ?: false - ) - ) - ) - ) + signClient.approve(proposal = proposal.toProposalFfi(), approvedNamespaces = sessionNamespaces.toSettleYttrium(), selfMetadata = selfAppMetaData.toYttrium()) } catch (e: Exception) { println("kobe: Approve Error: $e") onFailure(e) } }.await() - println("kobe: Result: $result") + println("kobe: Session Approve Result: $result") onSuccess() } } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/storage/proposal/ProposalStorageRepository.kt b/protocol/sign/src/main/kotlin/com/reown/sign/storage/proposal/ProposalStorageRepository.kt index b4e21fbca..20f818b1e 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/storage/proposal/ProposalStorageRepository.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/storage/proposal/ProposalStorageRepository.kt @@ -39,6 +39,7 @@ class ProposalStorageRepository( redirect, expiry?.seconds, scopedProperties, + pairingSymKey ) insertRequiredNamespace(requiredNamespaces, requestId) @@ -87,7 +88,8 @@ class ProposalStorageRepository( properties: Map?, redirect: String, expiry: Long?, - scoped_properties: Map? + scoped_properties: Map?, + pairing_sym_key: String ): ProposalVO { val requiredNamespaces: Map = getRequiredNamespaces(request_id) val optionalNamespaces: Map = getOptionalNamespaces(request_id) @@ -107,7 +109,8 @@ class ProposalStorageRepository( scopedProperties = scoped_properties, requiredNamespaces = requiredNamespaces, optionalNamespaces = optionalNamespaces, - expiry = if (expiry != null) Expiry(expiry) else null + expiry = if (expiry != null) Expiry(expiry) else null, + pairingSymKey = pairing_sym_key ) } diff --git a/protocol/sign/src/main/sqldelight/com/reown/sign/storage/data/dao/proposal/ProposalDao.sq b/protocol/sign/src/main/sqldelight/com/reown/sign/storage/data/dao/proposal/ProposalDao.sq index e6e875dca..6db40a88d 100644 --- a/protocol/sign/src/main/sqldelight/com/reown/sign/storage/data/dao/proposal/ProposalDao.sq +++ b/protocol/sign/src/main/sqldelight/com/reown/sign/storage/data/dao/proposal/ProposalDao.sq @@ -15,20 +15,21 @@ CREATE TABLE ProposalDao ( properties TEXT AS Map, redirect TEXT NOT NULL DEFAULT "", expiry INTEGER, - scoped_properties TEXT AS Map + scoped_properties TEXT AS Map, + pairing_sym_key TEXT NOT NULL ); insertOrAbortSession: -INSERT OR ABORT INTO ProposalDao(request_id, pairingTopic, name, description, url, icons, relay_protocol, relay_data, proposer_key, properties, redirect, expiry, scoped_properties) -VALUES (?,?,?,?,?,?,?,?,?,?,?, ?, ?); +INSERT OR ABORT INTO ProposalDao(request_id, pairingTopic, name, description, url, icons, relay_protocol, relay_data, proposer_key, properties, redirect, expiry, scoped_properties, pairing_sym_key) +VALUES (?,?,?,?,?,?,?,?,?,?,?, ?, ?, ?); getProposalByKey: -SELECT pd.request_id, pd.pairingTopic, pd.name, pd.description, pd.url, pd.icons, pd.relay_protocol, pd.relay_data, pd.proposer_key, pd.properties, pd.redirect, pd.expiry, pd.scoped_properties +SELECT pd.request_id, pd.pairingTopic, pd.name, pd.description, pd.url, pd.icons, pd.relay_protocol, pd.relay_data, pd.proposer_key, pd.properties, pd.redirect, pd.expiry, pd.scoped_properties, pd.pairing_sym_key FROM ProposalDao pd WHERE proposer_key = ?; getListOfProposalDaos: -SELECT pd.request_id, pd.pairingTopic, pd.name, pd.description, pd.url, pd.icons, pd.relay_protocol, pd.relay_data, pd.proposer_key, pd.properties, pd.redirect, pd.expiry, pd.scoped_properties +SELECT pd.request_id, pd.pairingTopic, pd.name, pd.description, pd.url, pd.icons, pd.relay_protocol, pd.relay_data, pd.proposer_key, pd.properties, pd.redirect, pd.expiry, pd.scoped_properties, pd.pairing_sym_key FROM ProposalDao pd; deleteProposal: @@ -36,6 +37,6 @@ DELETE FROM ProposalDao WHERE proposer_key = ?; getProposalByPairingTopic: -SELECT pd.request_id, pd.pairingTopic, pd.name, pd.description, pd.url, pd.icons, pd.relay_protocol, pd.relay_data, pd.proposer_key, pd.properties, pd.redirect, pd.expiry, pd.scoped_properties +SELECT pd.request_id, pd.pairingTopic, pd.name, pd.description, pd.url, pd.icons, pd.relay_protocol, pd.relay_data, pd.proposer_key, pd.properties, pd.redirect, pd.expiry, pd.scoped_properties, pd.pairing_sym_key FROM ProposalDao pd WHERE pairingTopic = ?; \ No newline at end of file diff --git a/protocol/sign/src/main/sqldelight/databases/14.db b/protocol/sign/src/main/sqldelight/databases/14.db new file mode 100644 index 0000000000000000000000000000000000000000..1dcf3ef074389535b00c1c4dee20209aa4923534 GIT binary patch literal 61440 zcmeI(-%i_B9Ki9or3twF>CNQZLlG)b=`>WK-AvlD)9O0dtdopV0F-#NDB@a$>ZPo;Pr#z$@{ z9@g&E45Rjw5VcxuTD=$4d-%%Mr-m!)uTfZ@v^ZV+_U)f@3;)&Tr#{yfUM~DS|IO@Q zbN|fln*Yu=W_~q4HU6l7n)+N1Cmn!t1Q0-AC~*G6T*F#iG(J8`-B*42OeTpR29Mov zv^4){&u(>X(QWsxUbv-xnoZfz_Y zH9zRdcgb;IoqHVjBn|U*XA~=EZM3}b{?%;5vTfsRBaeCOBt4LU>ZhB^J*mDuNMz2h zvf!$^IV~@)+EQFIRlbl5aj~&_e9Y$gFSi@k(vtDB^M@cU-hZ5IIDG`P%&t6 zERRoQk~$NTT#x3}c^kM#gK+BE-jj(J`%$XKoZf*vjZWgSEcR8(4pK%x*0s_<%cw9Z zB)@*lta?00W7+d#>5YvD@z{RS+HZHo-Me|)^?{lfl_zmtbrESNIX$`@hLvN>zT zwcBU+8VzfG-8eti##C{e;TS5GO>1E(+hQ=>zG{4~6@!Vy8`E{`#rh=TG6H9o9%Z z_-ZqnpR~#@nuaHq@64ua zfB*srAb0sjBL`Z1=52q1s}0tg_000IagfB*sr{11%Yh(`bb literal 0 HcmV?d00001 diff --git a/protocol/sign/src/main/sqldelight/migrations/13.sqm b/protocol/sign/src/main/sqldelight/migrations/13.sqm new file mode 100644 index 000000000..bc942e25e --- /dev/null +++ b/protocol/sign/src/main/sqldelight/migrations/13.sqm @@ -0,0 +1,5 @@ +-- migrates 13db to 14db + +-- CREATE V14 SCHEMA + +ALTER TABLE ProposalDao ADD COLUMN pairing_sym_key TEXT NOT NULL; \ No newline at end of file From b70a59c38a197057486197b6303b931e7468d2a3 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Fri, 8 Aug 2025 11:44:56 +0200 Subject: [PATCH 05/33] emit session request from rust client --- .../kotlin/com/reown/android/CoreProtocol.kt | 3 ++ .../kotlin/com/reown/sign/di/EngineModule.kt | 3 +- .../reown/sign/engine/domain/SignEngine.kt | 10 +++++- .../requests/OnSessionRequestUseCase.kt | 35 +++++++++++++++++-- 4 files changed, 46 insertions(+), 5 deletions(-) diff --git a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt index 92980ca9f..f665ac74d 100644 --- a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt +++ b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt @@ -21,6 +21,7 @@ import com.reown.android.internal.common.model.AppMetaData import com.reown.android.internal.common.model.ProjectId import com.reown.android.internal.common.model.Redirect import com.reown.android.internal.common.model.TelemetryEnabled +import com.reown.android.internal.common.scope import com.reown.android.internal.common.wcKoinApp import com.reown.android.pairing.client.PairingInterface import com.reown.android.pairing.client.PairingProtocol @@ -37,6 +38,7 @@ import com.reown.android.utils.plantTimber import com.reown.android.utils.projectId import com.reown.android.verify.client.VerifyClient import com.reown.android.verify.client.VerifyInterface +import kotlinx.coroutines.launch import org.koin.android.ext.koin.androidContext import org.koin.core.KoinApplication import org.koin.core.qualifier.named @@ -124,6 +126,7 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter require(projectId.isNotEmpty()) { "Project Id cannot be empty" } registerLogger(AndroidLogger()) signClient = SignClient(projectId = projectId) + setup( application = application, projectId = projectId, diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/di/EngineModule.kt b/protocol/sign/src/main/kotlin/com/reown/sign/di/EngineModule.kt index 202d856e9..96bdda14c 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/di/EngineModule.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/di/EngineModule.kt @@ -108,7 +108,8 @@ internal fun engineModule() = module { getPendingAuthenticateRequestUseCase = get(), insertEventUseCase = get(), linkModeJsonRpcInteractor = get(), - logger = get(named(AndroidCommonDITags.LOGGER)) + logger = get(named(AndroidCommonDITags.LOGGER)), + signClient = get(named(AndroidCommonDITags.SIGN_RUST_CLIENT)) ) } } \ No newline at end of file diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt index ede322c54..035e6cabf 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt @@ -98,6 +98,9 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import uniffi.yttrium.SessionRequestJsonRpcFfi +import uniffi.yttrium.SessionRequestListener +import uniffi.yttrium.SignClient import java.net.URI internal class SignEngine( @@ -151,7 +154,8 @@ internal class SignEngine( private val onSessionRequestResponseUseCase: OnSessionRequestResponseUseCase, private val insertEventUseCase: InsertTelemetryEventUseCase, private val linkModeJsonRpcInteractor: LinkModeJsonRpcInteractorInterface, - private val logger: Logger + private val logger: Logger, + private val signClient: SignClient ) : ProposeSessionUseCaseInterface by proposeSessionUseCase, SessionAuthenticateUseCaseInterface by authenticateSessionUseCase, PairUseCaseInterface by pairUseCase, @@ -212,6 +216,10 @@ internal class SignEngine( } fun setup() { + scope.launch { + signClient.registerSessionRequestListener(onSessionRequestUseCase) + } + handleLinkModeRequests() handleLinkModeResponses() diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt index c876eb4b9..ace3910ee 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt @@ -10,6 +10,7 @@ import com.reown.android.internal.common.model.Namespace import com.reown.android.internal.common.model.SDKError import com.reown.android.internal.common.model.Tags import com.reown.android.internal.common.model.TransportType +import com.reown.android.internal.common.model.Validation import com.reown.android.internal.common.model.WCRequest import com.reown.android.internal.common.model.type.EngineEvent import com.reown.android.internal.common.model.type.RelayJsonRpcInteractorInterface @@ -40,6 +41,8 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import uniffi.yttrium.SessionRequestJsonRpcFfi +import uniffi.yttrium.SessionRequestListener internal class OnSessionRequestUseCase( private val jsonRpcInteractor: RelayJsonRpcInteractorInterface, @@ -49,7 +52,7 @@ internal class OnSessionRequestUseCase( private val insertEventUseCase: InsertEventUseCase, private val clientId: String, private val logger: Logger -) { +) : SessionRequestListener { private val _events: MutableSharedFlow = MutableSharedFlow() val events: SharedFlow = _events.asSharedFlow() @@ -106,7 +109,12 @@ internal class OnSessionRequestUseCase( val url = sessionPeerAppMetaData?.url ?: String.Empty logger.log("Resolving session request attestation: ${System.currentTimeMillis()}") - resolveAttestationIdUseCase(request, url, linkMode = request.transportType == TransportType.LINK_MODE, appLink = sessionPeerAppMetaData?.redirect?.universal) { verifyContext -> + resolveAttestationIdUseCase( + request, + url, + linkMode = request.transportType == TransportType.LINK_MODE, + appLink = sessionPeerAppMetaData?.redirect?.universal + ) { verifyContext -> logger.log("Session request attestation resolved: ${System.currentTimeMillis()}") emitSessionRequest(params, request, sessionPeerAppMetaData, verifyContext) } @@ -122,6 +130,26 @@ internal class OnSessionRequestUseCase( } } + override fun onSessionRequest(topic: String, sessionRequest: SessionRequestJsonRpcFfi) { + println("jordan: Session Request: $topic ; $sessionRequest") + + val sessionRequestEvent = EngineDO.SessionRequestEvent( + request = EngineDO.SessionRequest( + topic = topic, + chainId = sessionRequest.params.chainId, + peerAppMetaData = null, + expiry = if (sessionRequest.params.request.expiry != null) Expiry(sessionRequest.params.request.expiry!!.toLong()) else null, + request = EngineDO.SessionRequest.JSONRPCRequest( + id = sessionRequest.id.toLong(), + method = sessionRequest.params.request.method, + params = sessionRequest.params.request.params + ) + ), + context = EngineDO.VerifyContext(1, "", Validation.UNKNOWN, "", null) + ) + scope.launch { _events.emit(sessionRequestEvent) } + } + private fun emitSessionRequest( params: SignParams.SessionRequestParams, request: WCRequest, @@ -132,7 +160,8 @@ internal class OnSessionRequestUseCase( val event = if (sessionRequestEventsQueue.isEmpty()) { sessionRequestEvent } else { - sessionRequestEventsQueue.find { event -> if (event.request.expiry != null) !event.request.expiry.isExpired() else true } ?: sessionRequestEvent + sessionRequestEventsQueue.find { event -> if (event.request.expiry != null) !event.request.expiry.isExpired() else true } + ?: sessionRequestEvent } sessionRequestEventsQueue.add(sessionRequestEvent) From 29a0240c22a3ef7ea0f7dcdd0a3e214f772ea7d7 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Fri, 8 Aug 2025 12:17:11 +0200 Subject: [PATCH 06/33] respond session request --- .../kotlin/com/reown/sign/di/CallsModule.kt | 62 ++++++- .../calls/RespondSessionRequestUseCase.kt | 171 ++++++++++-------- 2 files changed, 152 insertions(+), 81 deletions(-) diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/di/CallsModule.kt b/protocol/sign/src/main/kotlin/com/reown/sign/di/CallsModule.kt index 596c12394..58cc733da 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/di/CallsModule.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/di/CallsModule.kt @@ -142,7 +142,13 @@ internal fun callsModule() = module { ) } - single { SessionUpdateUseCase(jsonRpcInteractor = get(), sessionStorageRepository = get(), logger = get(named(AndroidCommonDITags.LOGGER))) } + single { + SessionUpdateUseCase( + jsonRpcInteractor = get(), + sessionStorageRepository = get(), + logger = get(named(AndroidCommonDITags.LOGGER)) + ) + } single { SessionRequestUseCase( @@ -170,7 +176,8 @@ internal fun callsModule() = module { metadataStorageRepository = get(), insertEventUseCase = get(), clientId = get(named(AndroidCommonDITags.CLIENT_ID)), - tvf = get() + tvf = get(), + signClient = get(named(AndroidCommonDITags.SIGN_RUST_CLIENT)) ) } @@ -182,19 +189,50 @@ internal fun callsModule() = module { pushMessageStorage = get(), ) - get>(named(AndroidCommonDITags.DECRYPT_USE_CASES))[Tags.SESSION_PROPOSE.id.toString()] = useCase + get>(named(AndroidCommonDITags.DECRYPT_USE_CASES))[Tags.SESSION_PROPOSE.id.toString()] = + useCase useCase } - single { PingUseCase(sessionStorageRepository = get(), jsonRpcInteractor = get(), logger = get(named(AndroidCommonDITags.LOGGER))) } + single { + PingUseCase( + sessionStorageRepository = get(), + jsonRpcInteractor = get(), + logger = get(named(AndroidCommonDITags.LOGGER)) + ) + } - single { EmitEventUseCase(jsonRpcInteractor = get(), sessionStorageRepository = get(), logger = get(named(AndroidCommonDITags.LOGGER))) } + single { + EmitEventUseCase( + jsonRpcInteractor = get(), + sessionStorageRepository = get(), + logger = get(named(AndroidCommonDITags.LOGGER)) + ) + } - single { ExtendSessionUseCase(jsonRpcInteractor = get(), sessionStorageRepository = get(), logger = get(named(AndroidCommonDITags.LOGGER))) } + single { + ExtendSessionUseCase( + jsonRpcInteractor = get(), + sessionStorageRepository = get(), + logger = get(named(AndroidCommonDITags.LOGGER)) + ) + } - single { DisconnectSessionUseCase(jsonRpcInteractor = get(), sessionStorageRepository = get(), logger = get(named(AndroidCommonDITags.LOGGER))) } + single { + DisconnectSessionUseCase( + jsonRpcInteractor = get(), + sessionStorageRepository = get(), + logger = get(named(AndroidCommonDITags.LOGGER)) + ) + } - single { GetSessionsUseCase(sessionStorageRepository = get(), metadataStorageRepository = get(), selfAppMetaData = get()) } + single { + GetSessionsUseCase( + sessionStorageRepository = get(), + metadataStorageRepository = get(), + selfAppMetaData = get() + ) + } single { GetPairingsUseCase(pairingInterface = get()) } @@ -204,7 +242,13 @@ internal fun callsModule() = module { single { GetPendingRequestsUseCaseByTopic(serializer = get(), jsonRpcHistory = get()) } - single { GetPendingSessionRequestByTopicUseCase(jsonRpcHistory = get(), serializer = get(), metadataStorageRepository = get()) } + single { + GetPendingSessionRequestByTopicUseCase( + jsonRpcHistory = get(), + serializer = get(), + metadataStorageRepository = get() + ) + } single { GetSessionProposalsUseCase(proposalStorageRepository = get()) } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/RespondSessionRequestUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/RespondSessionRequestUseCase.kt index 7b0742a55..dfd0e3f92 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/RespondSessionRequestUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/RespondSessionRequestUseCase.kt @@ -29,11 +29,14 @@ import com.reown.sign.engine.model.tvf.TVF import com.reown.sign.engine.sessionRequestEventsQueue import com.reown.sign.json_rpc.domain.GetPendingJsonRpcHistoryEntryByIdUseCase import com.reown.sign.storage.sequence.SessionStorageRepository +import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import uniffi.yttrium.SessionRequestResponseJsonRpcFfi +import uniffi.yttrium.SignClient internal class RespondSessionRequestUseCase( private val jsonRpcInteractor: RelayJsonRpcInteractorInterface, @@ -45,7 +48,8 @@ internal class RespondSessionRequestUseCase( private val metadataStorageRepository: MetadataStorageRepositoryInterface, private val insertEventUseCase: InsertEventUseCase, private val clientId: String, - private val tvf: TVF + private val tvf: TVF, + private val signClient: SignClient ) : RespondSessionRequestUseCaseInterface { private val _events: MutableSharedFlow = MutableSharedFlow() override val events: SharedFlow = _events.asSharedFlow() @@ -55,80 +59,103 @@ internal class RespondSessionRequestUseCase( onSuccess: () -> Unit, onFailure: (Throwable) -> Unit, ) = supervisorScope { - val topicWrapper = Topic(topic) - if (!sessionStorageRepository.isSessionValid(topicWrapper)) { - logger.error("Request response - invalid session: $topic, id: ${jsonRpcResponse.id}") - return@supervisorScope onFailure(CannotFindSequenceForTopic("$NO_SEQUENCE_FOR_TOPIC_MESSAGE$topic")) - } - val session = sessionStorageRepository.getSessionWithoutMetadataByTopic(topicWrapper) - .run { - val peerAppMetaData = metadataStorageRepository.getByTopicAndType(this.topic, AppMetaDataType.PEER) - this.copy(peerAppMetaData = peerAppMetaData) - } - - val pendingRequest = getPendingJsonRpcHistoryEntryByIdUseCase(jsonRpcResponse.id) - if (pendingRequest == null) { - logger.error("Request doesn't exist: $topic, id: ${jsonRpcResponse.id}") - return@supervisorScope onFailure(RequestExpiredException("This request has expired, id: ${jsonRpcResponse.id}")) - } - pendingRequest.params.expiry?.let { - if (Expiry(it).isExpired()) { - logger.error("Request Expired: $topic, id: ${jsonRpcResponse.id}") - return@supervisorScope onFailure(RequestExpiredException("This request has expired, id: ${jsonRpcResponse.id}")) - } - } - - if (session.transportType == TransportType.LINK_MODE && session.peerLinkMode == true) { - if (session.peerAppLink.isNullOrEmpty()) return@supervisorScope onFailure(IllegalStateException("App link is missing")) - try { - removePendingSessionRequestAndEmit(jsonRpcResponse.id) - linkModeJsonRpcInteractor.triggerResponse(Topic(topic), jsonRpcResponse, session.peerAppLink) - insertEventUseCase( - Props( - EventType.SUCCESS, - Tags.SESSION_REQUEST_LINK_MODE_RESPONSE.id.toString(), - Properties(correlationId = jsonRpcResponse.id, clientId = clientId, direction = Direction.SENT.state) - ) - ) - } catch (e: Exception) { - onFailure(e) - } - } else { - val tvfData = tvf.collect(pendingRequest.params.rpcMethod, pendingRequest.params.rpcParams, pendingRequest.params.chainId) - val txHashes = (jsonRpcResponse as? JsonRpcResponse.JsonRpcResult)?.let { - tvf.collectTxHashes( - pendingRequest.params.rpcMethod, - it.result.toString(), - pendingRequest.params.rpcParams - ) - } - val irnParams = IrnParams( - Tags.SESSION_REQUEST_RESPONSE, - Ttl(fiveMinutesInSeconds), - correlationId = jsonRpcResponse.id, - rpcMethods = tvfData.first, - contractAddresses = tvfData.second, - txHashes = txHashes, - chainId = tvfData.third + try { + val responseFfi = SessionRequestResponseJsonRpcFfi( + id = jsonRpcResponse.id.toULong(), + jsonrpc = "2.0", + result = (jsonRpcResponse as JsonRpcResponse.JsonRpcResult).result.toString() ) - logger.log("Sending session request response on topic: $topic, id: ${jsonRpcResponse.id}") - jsonRpcInteractor.publishJsonRpcResponse( - topic = Topic(topic), params = irnParams, response = jsonRpcResponse, - onSuccess = { - onSuccess() - logger.log("Session request response sent successfully on topic: $topic, id: ${jsonRpcResponse.id}") - scope.launch { - supervisorScope { - removePendingSessionRequestAndEmit(jsonRpcResponse.id) - } - } - }, - onFailure = { error -> - logger.error("Sending session response error: $error, id: ${jsonRpcResponse.id}") - onFailure(error) + + val result = async { + try { + signClient.respond(topic, responseFfi) + } catch (e: Exception) { + println("kobe: session request error: $e") + onFailure(e) } - ) + }.await() + + println("kobe: session request responded: $result") + onSuccess() + + } catch (e: Exception) { + println("kobe: session request error: $e") + onFailure(e) } +// val topicWrapper = Topic(topic) +// if (!sessionStorageRepository.isSessionValid(topicWrapper)) { +// logger.error("Request response - invalid session: $topic, id: ${jsonRpcResponse.id}") +// return@supervisorScope onFailure(CannotFindSequenceForTopic("$NO_SEQUENCE_FOR_TOPIC_MESSAGE$topic")) +// } +// val session = sessionStorageRepository.getSessionWithoutMetadataByTopic(topicWrapper) +// .run { +// val peerAppMetaData = metadataStorageRepository.getByTopicAndType(this.topic, AppMetaDataType.PEER) +// this.copy(peerAppMetaData = peerAppMetaData) +// } +// +// val pendingRequest = getPendingJsonRpcHistoryEntryByIdUseCase(jsonRpcResponse.id) +// if (pendingRequest == null) { +// logger.error("Request doesn't exist: $topic, id: ${jsonRpcResponse.id}") +// return@supervisorScope onFailure(RequestExpiredException("This request has expired, id: ${jsonRpcResponse.id}")) +// } +// pendingRequest.params.expiry?.let { +// if (Expiry(it).isExpired()) { +// logger.error("Request Expired: $topic, id: ${jsonRpcResponse.id}") +// return@supervisorScope onFailure(RequestExpiredException("This request has expired, id: ${jsonRpcResponse.id}")) +// } +// } +// +// if (session.transportType == TransportType.LINK_MODE && session.peerLinkMode == true) { +// if (session.peerAppLink.isNullOrEmpty()) return@supervisorScope onFailure(IllegalStateException("App link is missing")) +// try { +// removePendingSessionRequestAndEmit(jsonRpcResponse.id) +// linkModeJsonRpcInteractor.triggerResponse(Topic(topic), jsonRpcResponse, session.peerAppLink) +// insertEventUseCase( +// Props( +// EventType.SUCCESS, +// Tags.SESSION_REQUEST_LINK_MODE_RESPONSE.id.toString(), +// Properties(correlationId = jsonRpcResponse.id, clientId = clientId, direction = Direction.SENT.state) +// ) +// ) +// } catch (e: Exception) { +// onFailure(e) +// } +// } else { +// val tvfData = tvf.collect(pendingRequest.params.rpcMethod, pendingRequest.params.rpcParams, pendingRequest.params.chainId) +// val txHashes = (jsonRpcResponse as? JsonRpcResponse.JsonRpcResult)?.let { +// tvf.collectTxHashes( +// pendingRequest.params.rpcMethod, +// it.result.toString(), +// pendingRequest.params.rpcParams +// ) +// } +// val irnParams = IrnParams( +// Tags.SESSION_REQUEST_RESPONSE, +// Ttl(fiveMinutesInSeconds), +// correlationId = jsonRpcResponse.id, +// rpcMethods = tvfData.first, +// contractAddresses = tvfData.second, +// txHashes = txHashes, +// chainId = tvfData.third +// ) +// logger.log("Sending session request response on topic: $topic, id: ${jsonRpcResponse.id}") +// jsonRpcInteractor.publishJsonRpcResponse( +// topic = Topic(topic), params = irnParams, response = jsonRpcResponse, +// onSuccess = { +// onSuccess() +// logger.log("Session request response sent successfully on topic: $topic, id: ${jsonRpcResponse.id}") +// scope.launch { +// supervisorScope { +// removePendingSessionRequestAndEmit(jsonRpcResponse.id) +// } +// } +// }, +// onFailure = { error -> +// logger.error("Sending session response error: $error, id: ${jsonRpcResponse.id}") +// onFailure(error) +// } +// ) +// } } private suspend fun removePendingSessionRequestAndEmit(id: Long) { From 7244a40fe40e2579d74b2fb27317d51505c9764e Mon Sep 17 00:00:00 2001 From: jakubuid Date: Fri, 15 Aug 2025 09:47:49 +0200 Subject: [PATCH 07/33] add store session listener --- .../kotlin/com/reown/android/CoreProtocol.kt | 2 +- .../common/model/vo/sequence/SessionVO.kt | 3 +- .../kotlin/com/reown/sign/di/EngineModule.kt | 12 +- .../reown/sign/engine/domain/SessionStore.kt | 59 ++++++++++ .../reown/sign/engine/domain/SignEngine.kt | 16 +-- .../sign/engine/model/mapper/EngineMapper.kt | 104 +++++++++++++++++- .../use_case/calls/ApproveSessionUseCase.kt | 10 +- .../requests/OnSessionRequestUseCase.kt | 6 +- .../sequence/SessionStorageRepository.kt | 7 +- .../storage/data/dao/session/SessionDao.sq | 11 +- .../src/main/sqldelight/migrations/14.sqm | 5 + 11 files changed, 200 insertions(+), 35 deletions(-) create mode 100644 protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt create mode 100644 protocol/sign/src/main/sqldelight/migrations/14.sqm diff --git a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt index f665ac74d..ff52d6ce0 100644 --- a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt +++ b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt @@ -49,7 +49,7 @@ import uniffi.yttrium.registerLogger class AndroidLogger: Logger { override fun log(message: String) { - println("jordan: Message from Rust: $message") + println("kobe: Message from Rust: $message") } } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/common/model/vo/sequence/SessionVO.kt b/protocol/sign/src/main/kotlin/com/reown/sign/common/model/vo/sequence/SessionVO.kt index 3707a236c..628db13ea 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/common/model/vo/sequence/SessionVO.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/common/model/vo/sequence/SessionVO.kt @@ -33,7 +33,8 @@ internal data class SessionVO( val scopedProperties: Map? = null, val isAcknowledged: Boolean, val pairingTopic: String, - val transportType: TransportType? + val transportType: TransportType?, + val symKey: String? = null ) : Sequence { val isPeerController: Boolean = peerPublicKey?.keyAsHex == controllerKey?.keyAsHex val isSelfController: Boolean = selfPublicKey.keyAsHex == controllerKey?.keyAsHex diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/di/EngineModule.kt b/protocol/sign/src/main/kotlin/com/reown/sign/di/EngineModule.kt index 96bdda14c..7b8b1e6a1 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/di/EngineModule.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/di/EngineModule.kt @@ -4,6 +4,7 @@ package com.reown.sign.di import com.reown.android.internal.common.di.AndroidCommonDITags import com.reown.android.internal.common.signing.cacao.CacaoVerifier +import com.reown.sign.engine.domain.SessionStore import com.reown.sign.engine.domain.SignEngine import com.reown.sign.engine.domain.wallet_service.WalletServiceFinder import com.reown.sign.engine.domain.wallet_service.WalletServiceRequester @@ -56,6 +57,14 @@ internal fun engineModule() = module { ) } + single { + SessionStore( + sessionStorageRepository = get(), + metadataStorageRepository = get(), + selfAppMetaData = get() + ) + } + single { SignEngine( verifyContextStorageRepository = get(), @@ -109,7 +118,8 @@ internal fun engineModule() = module { insertEventUseCase = get(), linkModeJsonRpcInteractor = get(), logger = get(named(AndroidCommonDITags.LOGGER)), - signClient = get(named(AndroidCommonDITags.SIGN_RUST_CLIENT)) + signClient = get(named(AndroidCommonDITags.SIGN_RUST_CLIENT)), + sessionStore = get() ) } } \ No newline at end of file diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt new file mode 100644 index 000000000..c689f07dc --- /dev/null +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt @@ -0,0 +1,59 @@ +package com.reown.sign.engine.domain + +import com.reown.android.internal.common.model.AppMetaData +import com.reown.android.internal.common.model.AppMetaDataType +import com.reown.android.internal.common.storage.metadata.MetadataStorageRepositoryInterface +import com.reown.sign.engine.model.mapper.toEngineDO +import com.reown.sign.engine.model.mapper.toSessionFfi +import com.reown.sign.engine.model.mapper.toVO +import com.reown.sign.storage.sequence.SessionStorageRepository +import com.reown.utils.isSequenceValid +import uniffi.yttrium.SessionFfi +import uniffi.yttrium.SessionStore + +internal class SessionStore( + private val metadataStorageRepository: MetadataStorageRepositoryInterface, + private val sessionStorageRepository: SessionStorageRepository, + private val selfAppMetaData: AppMetaData +) : SessionStore { + override fun addSession(session: SessionFfi) { + println("kobe: addSession: $session") + + //TODO: request ID + val sessionVO = session.toVO() + sessionStorageRepository.insertSession(session = session.toVO(), requestId = session.requestId.toLong()) + metadataStorageRepository.insertOrAbortMetadata( + topic = sessionVO.topic, + appMetaData = selfAppMetaData, + appMetaDataType = AppMetaDataType.SELF + ) + metadataStorageRepository.insertOrAbortMetadata( + topic = sessionVO.topic, + appMetaData = sessionVO.peerAppMetaData!!, + appMetaDataType = AppMetaDataType.PEER + ) + } + + override fun getAllSessions(): List { + println("kobe: get all sessions") + + return sessionStorageRepository.getListOfSessionVOsWithoutMetadata() + .filter { session -> session.isAcknowledged && session.expiry.isSequenceValid() } + .map { session -> + session.copy( + selfAppMetaData = selfAppMetaData, + peerAppMetaData = metadataStorageRepository.getByTopicAndType(session.topic, AppMetaDataType.PEER) + ) + } + .map { session -> session.toSessionFfi() } + } + + override fun deleteSession(topic: String) { + println("kobe: deleteSession: $topic") + } + + override fun getSession(topic: String): SessionFfi? { + println("kobe: get session: $topic") + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt index 035e6cabf..e7f52974b 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt @@ -5,7 +5,6 @@ package com.reown.sign.engine.domain import com.reown.android.internal.common.crypto.kmr.KeyManagementRepository import com.reown.android.internal.common.json_rpc.domain.link_mode.LinkModeJsonRpcInteractorInterface import com.reown.android.internal.common.model.AppMetaDataType -import com.reown.android.internal.common.model.Expiry import com.reown.android.internal.common.model.SDKError import com.reown.android.internal.common.model.Validation import com.reown.android.internal.common.model.type.EngineEvent @@ -23,19 +22,14 @@ import com.reown.android.pulse.model.properties.Props import com.reown.android.push.notifications.DecryptMessageUseCaseInterface import com.reown.android.relay.WSSConnectionState import com.reown.android.verify.model.VerifyContext -import com.reown.foundation.common.model.Topic import com.reown.foundation.util.Logger import com.reown.sign.common.model.vo.clientsync.session.params.SignParams -import com.reown.sign.common.model.vo.proposal.ProposalVO import com.reown.sign.engine.model.EngineDO -import com.reown.sign.engine.model.mapper.toEngine import com.reown.sign.engine.model.mapper.toEngineDO import com.reown.sign.engine.model.mapper.toExpiredProposal import com.reown.sign.engine.model.mapper.toExpiredSessionRequest -import com.reown.sign.engine.model.mapper.toNamespacesVOOptional import com.reown.sign.engine.model.mapper.toSessionRequest import com.reown.sign.engine.model.mapper.toVO -import com.reown.sign.engine.model.mapper.toYttrium import com.reown.sign.engine.sessionRequestEventsQueue import com.reown.sign.engine.use_case.calls.ApproveSessionAuthenticateUseCaseInterface import com.reown.sign.engine.use_case.calls.ApproveSessionUseCaseInterface @@ -80,7 +74,6 @@ import com.reown.sign.json_rpc.model.JsonRpcMethod import com.reown.sign.storage.authenticate.AuthenticateResponseTopicRepository import com.reown.sign.storage.proposal.ProposalStorageRepository import com.reown.sign.storage.sequence.SessionStorageRepository -import com.reown.util.bytesToHex import com.reown.utils.Empty import com.reown.utils.isSequenceValid import kotlinx.coroutines.Dispatchers @@ -98,10 +91,7 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -import uniffi.yttrium.SessionRequestJsonRpcFfi -import uniffi.yttrium.SessionRequestListener import uniffi.yttrium.SignClient -import java.net.URI internal class SignEngine( private val jsonRpcInteractor: RelayJsonRpcInteractorInterface, @@ -155,7 +145,8 @@ internal class SignEngine( private val insertEventUseCase: InsertTelemetryEventUseCase, private val linkModeJsonRpcInteractor: LinkModeJsonRpcInteractorInterface, private val logger: Logger, - private val signClient: SignClient + private val signClient: SignClient, + private val sessionStore: SessionStore ) : ProposeSessionUseCaseInterface by proposeSessionUseCase, SessionAuthenticateUseCaseInterface by authenticateSessionUseCase, PairUseCaseInterface by pairUseCase, @@ -217,7 +208,8 @@ internal class SignEngine( fun setup() { scope.launch { - signClient.registerSessionRequestListener(onSessionRequestUseCase) + signClient.registerSignListener(onSessionRequestUseCase) + signClient.registerSessionStore(sessionStore) } handleLinkModeRequests() diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt index 0741d65a3..809363249 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/model/mapper/EngineMapper.kt @@ -28,6 +28,7 @@ import com.reown.sign.common.model.vo.proposal.ProposalVO import com.reown.sign.common.model.vo.sequence.SessionVO import com.reown.sign.engine.model.EngineDO import com.reown.sign.engine.model.ValidationError +import com.reown.sign.engine.model.mapper.toSettleYttrium import com.reown.sign.json_rpc.model.JsonRpcMethod import com.reown.util.Empty import com.reown.util.bytesToHex @@ -36,6 +37,7 @@ import uniffi.yttrium.Metadata import uniffi.yttrium.ProposalNamespace import uniffi.yttrium.Redirect import uniffi.yttrium.Relay +import uniffi.yttrium.SessionFfi import uniffi.yttrium.SessionProposalFfi import uniffi.yttrium.SettleNamespace import java.net.URI @@ -441,7 +443,7 @@ internal fun ProposalVO.toProposalFfi(): SessionProposalFfi = scopedProperties = scopedProperties, expiryTimestamp = expiry?.seconds?.toULong(), relays = listOf(Relay(relayProtocol)), - metadata = uniffi.yttrium.Metadata( + metadata = Metadata( name = name, verifyUrl = null, description = description, @@ -453,4 +455,102 @@ internal fun ProposalVO.toProposalFfi(): SessionProposalFfi = linkMode = appMetaData.redirect?.linkMode ?: false ) ) - ) \ No newline at end of file + ) + +internal fun Map.toProposalNamespaceVO(): Map = + this.mapValues { (_, namespace) -> + Namespace.Proposal(namespace.chains, namespace.methods, namespace.events) + } + +internal fun Map.toSessionVO(): Map = + this.mapValues { (_, namespace) -> + Namespace.Session(chains = namespace.chains, methods = namespace.methods, events = namespace.events, accounts = namespace.accounts) + } + +internal fun Map.toYttriumSettle(): Map = + this.mapValues { (_, namespace) -> + SettleNamespace( + chains = namespace.chains ?: emptyList(), + methods = namespace.methods, + events = namespace.events, + accounts = namespace.accounts + ) + } + +internal fun SessionFfi.toVO(): SessionVO { + return SessionVO( + topic = Topic(this.topic), + expiry = Expiry(this.expiry.toLong()), + relayProtocol = "irn", + relayData = null, + controllerKey = PublicKey(this.controllerKey?.bytesToHex() ?: ""), + selfPublicKey = PublicKey(this.selfPublicKey.bytesToHex()), + selfAppMetaData = AppMetaData( + name = this.selfMetaData.name, + description = this.selfMetaData.description, + url = this.selfMetaData.url, + icons = this.selfMetaData.icons, + redirect = com.reown.android.internal.common.model.Redirect( + native = this.selfMetaData.redirect?.native, + linkMode = this.selfMetaData.redirect?.linkMode ?: false, + universal = this.selfMetaData.redirect?.universal + ) + ), + peerPublicKey = PublicKey(this.peerPublicKey?.bytesToHex() ?: ""), + peerAppMetaData = AppMetaData( + name = this.peerMetaData?.name ?: "", + description = this.peerMetaData?.description ?: "", + url = this.peerMetaData?.url ?: "", + icons = this.peerMetaData?.icons ?: emptyList(), + redirect = com.reown.android.internal.common.model.Redirect( + native = this.peerMetaData?.redirect?.native, + linkMode = this.peerMetaData?.redirect?.linkMode ?: false, + universal = this.peerMetaData?.redirect?.universal + ) + ), + sessionNamespaces = this.sessionNamespaces.toSessionVO(), + requiredNamespaces = this.requiredNamespaces.toProposalNamespaceVO(), + optionalNamespaces = this.optionalNamespaces?.toProposalNamespaceVO(), + properties = this.properties, + scopedProperties = this.scopedProperties, + isAcknowledged = true, + pairingTopic = this.pairingTopic, + transportType = TransportType.RELAY, //TODO change for LinkMode + symKey = this.sessionSymKey.bytesToHex() + ) +} + +internal fun SessionVO.toSessionFfi(): SessionFfi { + return SessionFfi( + topic = this.topic.value, + expiry = this.expiry.seconds.toULong(), + controllerKey = this.controllerKey?.keyAsHex?.hexToBytes(), + selfPublicKey = this.selfPublicKey.keyAsHex.hexToBytes(), + selfMetaData = this.selfAppMetaData?.toYttrium() ?: Metadata( + name = this.selfAppMetaData?.name ?: "", + verifyUrl = null, + description = this.selfAppMetaData?.description ?: "", + icons = this.selfAppMetaData?.icons ?: emptyList(), + url = this.selfAppMetaData?.url ?: "", + redirect = Redirect( + native = this.selfAppMetaData?.redirect?.native ?: "", + universal = this.selfAppMetaData?.redirect?.universal ?: "", + linkMode = this.selfAppMetaData?.redirect?.linkMode ?: false, + ) + ), + peerPublicKey = this.peerPublicKey?.keyAsHex?.hexToBytes(), + peerMetaData = this.peerAppMetaData?.toYttrium(), + sessionNamespaces = this.sessionNamespaces.toYttriumSettle(), + requiredNamespaces = this.requiredNamespaces.toProposalYttrium(), + optionalNamespaces = this.optionalNamespaces?.toProposalYttrium(), + properties = this.properties, + scopedProperties = this.scopedProperties, + pairingTopic = this.pairingTopic, + sessionSymKey = this.symKey?.hexToBytes() ?: ByteArray(0), + relayProtocol = this.relayProtocol, + relayData = this.relayData, + isAcknowledged = this.isAcknowledged, + transportType = uniffi.yttrium.TransportType.RELAY, //TODO: change for link mode + requestId = 0.toULong() + ) +} diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt index e67598bcf..271798e94 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/ApproveSessionUseCase.kt @@ -1,6 +1,5 @@ package com.reown.sign.engine.use_case.calls -import com.reown.android.internal.Validator import com.reown.android.internal.common.crypto.kmr.KeyManagementRepository import com.reown.android.internal.common.model.AppMetaData import com.reown.android.internal.common.model.type.RelayJsonRpcInteractorInterface @@ -11,17 +10,12 @@ import com.reown.android.pulse.domain.InsertTelemetryEventUseCase import com.reown.foundation.util.Logger import com.reown.sign.engine.model.EngineDO import com.reown.sign.engine.model.mapper.toProposalFfi -import com.reown.sign.engine.model.mapper.toProposalYttrium import com.reown.sign.engine.model.mapper.toSettleYttrium import com.reown.sign.engine.model.mapper.toYttrium import com.reown.sign.storage.proposal.ProposalStorageRepository import com.reown.sign.storage.sequence.SessionStorageRepository -import com.reown.util.hexToBytes import kotlinx.coroutines.async import kotlinx.coroutines.supervisorScope -import uniffi.yttrium.Redirect -import uniffi.yttrium.Relay -import uniffi.yttrium.SessionProposalFfi import uniffi.yttrium.SignClient internal class ApproveSessionUseCase( @@ -49,7 +43,7 @@ internal class ApproveSessionUseCase( // val trace: MutableList = mutableListOf() // trace.add(Trace.Session.SESSION_APPROVE_STARTED).also { logger.log(Trace.Session.SESSION_APPROVE_STARTED) } val proposal = proposalStorageRepository.getProposalByKey(proposerPublicKey) - val result = async { + val sessionFfi = async { try { signClient.approve(proposal = proposal.toProposalFfi(), approvedNamespaces = sessionNamespaces.toSettleYttrium(), selfMetadata = selfAppMetaData.toYttrium()) } catch (e: Exception) { @@ -58,7 +52,7 @@ internal class ApproveSessionUseCase( } }.await() - println("kobe: Session Approve Result: $result") + println("kobe: Session Approve Result: $sessionFfi") onSuccess() } } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt index ace3910ee..1c8f8a7b4 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt @@ -42,7 +42,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import uniffi.yttrium.SessionRequestJsonRpcFfi -import uniffi.yttrium.SessionRequestListener +import uniffi.yttrium.SignListener internal class OnSessionRequestUseCase( private val jsonRpcInteractor: RelayJsonRpcInteractorInterface, @@ -52,7 +52,7 @@ internal class OnSessionRequestUseCase( private val insertEventUseCase: InsertEventUseCase, private val clientId: String, private val logger: Logger -) : SessionRequestListener { +) : SignListener { private val _events: MutableSharedFlow = MutableSharedFlow() val events: SharedFlow = _events.asSharedFlow() @@ -131,7 +131,7 @@ internal class OnSessionRequestUseCase( } override fun onSessionRequest(topic: String, sessionRequest: SessionRequestJsonRpcFfi) { - println("jordan: Session Request: $topic ; $sessionRequest") + println("kobe: Session Request: $topic ; $sessionRequest") val sessionRequestEvent = EngineDO.SessionRequestEvent( request = EngineDO.SessionRequest( diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/storage/sequence/SessionStorageRepository.kt b/protocol/sign/src/main/kotlin/com/reown/sign/storage/sequence/SessionStorageRepository.kt index e8ed1b2dc..bd34b6d23 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/storage/sequence/SessionStorageRepository.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/storage/sequence/SessionStorageRepository.kt @@ -80,7 +80,8 @@ internal class SessionStorageRepository( is_acknowledged = isAcknowledged, properties = properties, scoped_properties = scopedProperties, - transport_type = transportType + transport_type = transportType, + sym_key = symKey ) } @@ -225,6 +226,7 @@ internal class SessionStorageRepository( properties: Map?, transportType: TransportType?, scoped_properties: Map?, + sym_key: String? ): SessionVO { val sessionNamespaces: Map = getSessionNamespaces(id) val requiredNamespaces: Map = getRequiredNamespaces(id) @@ -247,7 +249,8 @@ internal class SessionStorageRepository( properties = properties, scopedProperties = scoped_properties, pairingTopic = pairingTopic, - transportType = transportType + transportType = transportType, + symKey = sym_key ) } diff --git a/protocol/sign/src/main/sqldelight/com/reown/sign/storage/data/dao/session/SessionDao.sq b/protocol/sign/src/main/sqldelight/com/reown/sign/storage/data/dao/session/SessionDao.sq index 3c31e7429..ce64261ca 100644 --- a/protocol/sign/src/main/sqldelight/com/reown/sign/storage/data/dao/session/SessionDao.sq +++ b/protocol/sign/src/main/sqldelight/com/reown/sign/storage/data/dao/session/SessionDao.sq @@ -16,12 +16,13 @@ CREATE TABLE SessionDao( is_acknowledged INTEGER AS Boolean NOT NULL, properties TEXT AS Map, transport_type TEXT AS TransportType, - scoped_properties TEXT AS Map + scoped_properties TEXT AS Map, + sym_key TEXT ); insertOrAbortSession: -INSERT OR ABORT INTO SessionDao(topic, pairingTopic, expiry, relay_protocol, relay_data, controller_key, self_participant, peer_participant, is_acknowledged, properties, transport_type, scoped_properties) -VALUES (?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?); +INSERT OR ABORT INTO SessionDao(topic, pairingTopic, expiry, relay_protocol, relay_data, controller_key, self_participant, peer_participant, is_acknowledged, properties, transport_type, scoped_properties, sym_key) +VALUES (?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?); lastInsertedRow: SELECT id @@ -29,11 +30,11 @@ FROM SessionDao WHERE id = (SELECT MAX(id) FROM SessionDao); getListOfSessionDaos: -SELECT sd.id, sd.topic, sd.expiry, sd.relay_protocol, sd.relay_data, sd.controller_key, sd.self_participant, sd.peer_participant, sd.is_acknowledged, sd.pairingTopic, sd.properties, sd.transport_type, sd.scoped_properties +SELECT sd.id, sd.topic, sd.expiry, sd.relay_protocol, sd.relay_data, sd.controller_key, sd.self_participant, sd.peer_participant, sd.is_acknowledged, sd.pairingTopic, sd.properties, sd.transport_type, sd.scoped_properties, sd.sym_key FROM SessionDao sd; getSessionByTopic: -SELECT sd.id, sd.topic, sd.expiry, sd.relay_protocol, sd.relay_data, sd.controller_key, sd.self_participant, sd.peer_participant, sd.is_acknowledged, sd.pairingTopic, sd.properties, sd.transport_type, sd.scoped_properties +SELECT sd.id, sd.topic, sd.expiry, sd.relay_protocol, sd.relay_data, sd.controller_key, sd.self_participant, sd.peer_participant, sd.is_acknowledged, sd.pairingTopic, sd.properties, sd.transport_type, sd.scoped_properties, sd.sym_key FROM SessionDao sd WHERE topic = ?; diff --git a/protocol/sign/src/main/sqldelight/migrations/14.sqm b/protocol/sign/src/main/sqldelight/migrations/14.sqm new file mode 100644 index 000000000..20bda25bb --- /dev/null +++ b/protocol/sign/src/main/sqldelight/migrations/14.sqm @@ -0,0 +1,5 @@ +-- migrates 14db to 15db + +-- CREATE V15 SCHEMA + +ALTER TABLE SessionDao ADD COLUMN sym_key TEXT; \ No newline at end of file From ae18d2e1bd5512ce901d0377b9512e3c237cf282 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Fri, 15 Aug 2025 11:33:03 +0200 Subject: [PATCH 08/33] improve setting a key for client id --- .../kotlin/com/reown/android/CoreProtocol.kt | 28 +++++++- .../internal/common/di/CoreCryptoModule.kt | 18 +++-- .../clientid/ClientIdJwtRepositoryAndroid.kt | 66 +++++++++++++++---- 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt index ff52d6ce0..4ba55bcbb 100644 --- a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt +++ b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt @@ -17,11 +17,14 @@ import com.reown.android.internal.common.di.pulseModule import com.reown.android.internal.common.di.pushModule import com.reown.android.internal.common.explorer.ExplorerInterface import com.reown.android.internal.common.explorer.ExplorerProtocol +import com.reown.android.internal.common.jwt.clientid.ClientIdJwtRepositoryAndroid +import com.reown.android.internal.common.jwt.clientid.GetKeyPair import com.reown.android.internal.common.model.AppMetaData import com.reown.android.internal.common.model.ProjectId import com.reown.android.internal.common.model.Redirect import com.reown.android.internal.common.model.TelemetryEnabled import com.reown.android.internal.common.scope +import com.reown.android.internal.common.storage.key_chain.KeyStore import com.reown.android.internal.common.wcKoinApp import com.reown.android.pairing.client.PairingInterface import com.reown.android.pairing.client.PairingProtocol @@ -38,7 +41,18 @@ import com.reown.android.utils.plantTimber import com.reown.android.utils.projectId import com.reown.android.verify.client.VerifyClient import com.reown.android.verify.client.VerifyInterface +import com.reown.foundation.common.model.PrivateKey +import com.reown.foundation.common.model.PublicKey +import com.reown.foundation.crypto.data.repository.BaseClientIdJwtRepository.Companion.CLIENT_ID_KEYPAIR_TAG +import com.reown.util.bytesToHex +import com.reown.util.hexToBytes import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import org.bouncycastle.crypto.AsymmetricCipherKeyPair +import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator +import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters +import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters import org.koin.android.ext.koin.androidContext import org.koin.core.KoinApplication import org.koin.core.qualifier.named @@ -46,8 +60,9 @@ import org.koin.dsl.module import uniffi.yttrium.Logger import uniffi.yttrium.SignClient import uniffi.yttrium.registerLogger +import java.security.SecureRandom -class AndroidLogger: Logger { +class AndroidLogger : Logger { override fun log(message: String) { println("kobe: Message from Rust: $message") } @@ -57,6 +72,7 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter override val Pairing: PairingInterface = PairingProtocol(koinApp) override val PairingController: PairingControllerInterface = PairingController(koinApp) override var Relay = RelayClient(koinApp) + private val keyStore: GetKeyPair by lazy { koinApp.koin.get() } @Deprecated(message = "Replaced with Push") override val Echo: PushInterface = PushClient @@ -172,6 +188,16 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter coreCryptoModule(), ) + + val (_, privateKey) = keyStore.getKeyPair() + scope.launch { + supervisorScope { + println("kobe: Setting key: $privateKey") + signClient.setKey(privateKey.hexToBytes()) + } + } + + if (relay == null) { Relay.initialize(connectionType) { error -> onError(Core.Model.Error(error)) } } diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreCryptoModule.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreCryptoModule.kt index 74d004dd0..88f0bc3fc 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreCryptoModule.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreCryptoModule.kt @@ -12,6 +12,7 @@ import com.reown.android.internal.common.crypto.codec.Codec import com.reown.android.internal.common.crypto.kmr.BouncyCastleKeyManagementRepository import com.reown.android.internal.common.crypto.kmr.KeyManagementRepository import com.reown.android.internal.common.jwt.clientid.ClientIdJwtRepositoryAndroid +import com.reown.android.internal.common.jwt.clientid.GetKeyPair import com.reown.android.internal.common.storage.key_chain.KeyChain import com.reown.foundation.crypto.data.repository.ClientIdJwtRepository import com.reown.foundation.util.Logger @@ -29,12 +30,18 @@ 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) @@ -70,7 +77,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() } @@ -95,7 +103,9 @@ fun coreCryptoModule(sharedPrefsFile: String = SHARED_PREFS_FILE, keyStoreAlias: single { KeyChain(sharedPreferences = get()) } - single { ClientIdJwtRepositoryAndroid(keyChain = get(), signClient = get(named(AndroidCommonDITags.SIGN_RUST_CLIENT))) } + single { ClientIdJwtRepositoryAndroid(keyChain = get()) } + + single { GetKeyPair(keyChain = get()) } single { BouncyCastleKeyManagementRepository(keyChain = get()) } diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt index 38ca2bb0f..cf8094351 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/jwt/clientid/ClientIdJwtRepositoryAndroid.kt @@ -3,17 +3,21 @@ package com.reown.android.internal.common.jwt.clientid import com.reown.android.internal.common.exception.CannotFindKeyPairException -import com.reown.android.internal.common.scope import com.reown.android.internal.common.storage.key_chain.KeyStore import com.reown.foundation.common.model.PrivateKey import com.reown.foundation.common.model.PublicKey import com.reown.foundation.crypto.data.repository.BaseClientIdJwtRepository -import com.reown.util.hexToBytes -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import uniffi.yttrium.SignClient +import com.reown.foundation.crypto.data.repository.BaseClientIdJwtRepository.Companion.CLIENT_ID_KEYPAIR_TAG +import com.reown.util.bytesToHex +import org.bouncycastle.crypto.AsymmetricCipherKeyPair +import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator +import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters +import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters +import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters +import java.security.SecureRandom -internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore, private val signClient: SignClient) : BaseClientIdJwtRepository() { +//TODO: Remove +internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore) : BaseClientIdJwtRepository() { override fun setKeyPair(key: String, privateKey: PrivateKey, publicKey: PublicKey) { keyChain.setKeys(CLIENT_ID_KEYPAIR_TAG, privateKey, publicKey) @@ -24,13 +28,7 @@ internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore, priv val (privateKey, publicKey) = keyChain.getKeys(CLIENT_ID_KEYPAIR_TAG) ?: throw CannotFindKeyPairException("No key pair for given tag: $CLIENT_ID_KEYPAIR_TAG") - runBlocking { - println("kobe: Setting key: $privateKey") - signClient.setKey(privateKey.hexToBytes()) - - publicKey to privateKey - } - + publicKey to privateKey } else { generateAndStoreClientIdKeyPair() } @@ -39,4 +37,46 @@ internal class ClientIdJwtRepositoryAndroid(private val keyChain: KeyStore, priv private fun doesKeyPairExist(): Boolean { return keyChain.checkKeys(CLIENT_ID_KEYPAIR_TAG) } +} + +internal class GetKeyPair(private val keyChain: KeyStore) { + + fun setKeyPair(key: String, privateKey: PrivateKey, publicKey: PublicKey) { + keyChain.setKeys(KEYPAIR_TAG, privateKey, publicKey) + } + + fun getKeyPair(): Pair { + return if (doesKeyPairExist()) { + val (privateKey, publicKey) = keyChain.getKeys(KEYPAIR_TAG) + ?: throw CannotFindKeyPairException("No key pair for given tag: $KEYPAIR_TAG") + + publicKey to privateKey + } else { + generateAndStoreKeyPair() + } + } + + private fun generateAndStoreKeyPair(): Pair { + val secureRandom = SecureRandom() + val keyPair: AsymmetricCipherKeyPair = Ed25519KeyPairGenerator().run { + this.init(Ed25519KeyGenerationParameters(secureRandom)) + this.generateKeyPair() + } + val publicKeyParameters = keyPair.public as Ed25519PublicKeyParameters + val privateKeyParameters = keyPair.private as Ed25519PrivateKeyParameters + val publicKey = PublicKey(publicKeyParameters.encoded.bytesToHex()) + val privateKey = PrivateKey(privateKeyParameters.encoded.bytesToHex()) + + setKeyPair(KEYPAIR_TAG, privateKey, publicKey) + + return publicKey.keyAsHex to privateKey.keyAsHex + } + + private fun doesKeyPairExist(): Boolean { + return keyChain.checkKeys(KEYPAIR_TAG) + } + + companion object { + const val KEYPAIR_TAG = "key_keypair_rust" + } } \ No newline at end of file From 6a0ff5590915641ee30f4e8286afd804515e1ce7 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Fri, 15 Aug 2025 11:37:27 +0200 Subject: [PATCH 09/33] more logs --- .../kotlin/com/reown/sign/engine/domain/SessionStore.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt index c689f07dc..f32d59455 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt @@ -17,9 +17,8 @@ internal class SessionStore( private val selfAppMetaData: AppMetaData ) : SessionStore { override fun addSession(session: SessionFfi) { - println("kobe: addSession: $session") + println("kobe: SessionStore: addSession: $session") - //TODO: request ID val sessionVO = session.toVO() sessionStorageRepository.insertSession(session = session.toVO(), requestId = session.requestId.toLong()) metadataStorageRepository.insertOrAbortMetadata( @@ -35,7 +34,7 @@ internal class SessionStore( } override fun getAllSessions(): List { - println("kobe: get all sessions") + println("kobe: SessionStore: get all sessions") return sessionStorageRepository.getListOfSessionVOsWithoutMetadata() .filter { session -> session.isAcknowledged && session.expiry.isSequenceValid() } @@ -49,11 +48,11 @@ internal class SessionStore( } override fun deleteSession(topic: String) { - println("kobe: deleteSession: $topic") + println("kobe: SessionStore: deleteSession: $topic") } override fun getSession(topic: String): SessionFfi? { - println("kobe: get session: $topic") + println("kobe: SessionStore: get session: $topic") TODO("Not yet implemented") } } \ No newline at end of file From 6ee05605fcbd45271f2423a600284a81a6581d5e Mon Sep 17 00:00:00 2001 From: jakubuid Date: Mon, 18 Aug 2025 12:11:25 +0200 Subject: [PATCH 10/33] add session delete handling --- .../com/reown/sign/engine/domain/SessionStore.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt index f32d59455..aa8ebc278 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt @@ -3,13 +3,14 @@ package com.reown.sign.engine.domain import com.reown.android.internal.common.model.AppMetaData import com.reown.android.internal.common.model.AppMetaDataType import com.reown.android.internal.common.storage.metadata.MetadataStorageRepositoryInterface -import com.reown.sign.engine.model.mapper.toEngineDO +import com.reown.foundation.common.model.Topic import com.reown.sign.engine.model.mapper.toSessionFfi import com.reown.sign.engine.model.mapper.toVO import com.reown.sign.storage.sequence.SessionStorageRepository import com.reown.utils.isSequenceValid import uniffi.yttrium.SessionFfi import uniffi.yttrium.SessionStore +import kotlin.collections.filter internal class SessionStore( private val metadataStorageRepository: MetadataStorageRepositoryInterface, @@ -49,10 +50,17 @@ internal class SessionStore( override fun deleteSession(topic: String) { println("kobe: SessionStore: deleteSession: $topic") + + sessionStorageRepository.deleteSession(topic = Topic(topic)) } override fun getSession(topic: String): SessionFfi? { println("kobe: SessionStore: get session: $topic") - TODO("Not yet implemented") + + return sessionStorageRepository.getSessionWithoutMetadataByTopic(topic = Topic(topic)) + .run { + val peerAppMetaData = metadataStorageRepository.getByTopicAndType(this.topic, AppMetaDataType.PEER) + this.copy(peerAppMetaData = peerAppMetaData) + }.toSessionFfi() } -} \ No newline at end of file +} From 899ef2b256d70591252f03b67f172fd7a658fcd6 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Mon, 18 Aug 2025 13:54:14 +0200 Subject: [PATCH 11/33] call online when app going to foreground --- .../connection/DefaultConnectionLifecycle.kt | 33 +++++++------------ .../connection/ManualConnectionLifecycle.kt | 2 ++ .../internal/common/di/CoreNetworkModule.kt | 2 +- .../reown/sign/engine/domain/SignEngine.kt | 1 + 4 files changed, 16 insertions(+), 22 deletions(-) diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/connection/DefaultConnectionLifecycle.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/connection/DefaultConnectionLifecycle.kt index 031ed4b72..9ef3b53c0 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/connection/DefaultConnectionLifecycle.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/connection/DefaultConnectionLifecycle.kt @@ -18,11 +18,14 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import uniffi.yttrium.SignClient import java.util.concurrent.TimeUnit internal class DefaultConnectionLifecycle( application: Application, - private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry() + private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(), + private val signClient: SignClient ) : Lifecycle by lifecycleRegistry, ConnectionLifecycle { private val job = SupervisorJob() private var scope = CoroutineScope(job + Dispatchers.Default) @@ -35,38 +38,26 @@ 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 + supervisorScope { + signClient.online() + } } } diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/connection/ManualConnectionLifecycle.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/connection/ManualConnectionLifecycle.kt index 8b40415cb..9f4ada824 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/connection/ManualConnectionLifecycle.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/connection/ManualConnectionLifecycle.kt @@ -25,6 +25,7 @@ internal class ManualConnectionLifecycle( private val _onResume = MutableStateFlow(null) override val onResume: StateFlow = _onResume.asStateFlow() + //TODO: call method from Rust Client fun connect() { scope.launch { connectionMutex.withLock { @@ -33,6 +34,7 @@ internal class ManualConnectionLifecycle( } } + //TODO: call method from Rust Client fun disconnect() { scope.launch { connectionMutex.withLock { diff --git a/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreNetworkModule.kt b/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreNetworkModule.kt index ac7bddb96..f385e4960 100644 --- a/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreNetworkModule.kt +++ b/core/android/src/main/kotlin/com/reown/android/internal/common/di/CoreNetworkModule.kt @@ -111,7 +111,7 @@ fun coreAndroidNetworkModule( } single(named(AndroidCommonDITags.DEFAULT_CONNECTION_LIFECYCLE)) { - DefaultConnectionLifecycle(androidApplication()) + DefaultConnectionLifecycle(androidApplication(), signClient = get(named(AndroidCommonDITags.SIGN_RUST_CLIENT))) } single { ConditionalExponentialBackoffStrategy(INIT_BACKOFF_MILLIS, TimeUnit.SECONDS.toMillis(MAX_BACKOFF_SEC), connectionType) } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt index e7f52974b..1996d3533 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt @@ -92,6 +92,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import uniffi.yttrium.SignClient +import kotlin.math.sign internal class SignEngine( private val jsonRpcInteractor: RelayJsonRpcInteractorInterface, From a9ce32d7749836752ed4607d2c3535726e6febc6 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Mon, 18 Aug 2025 14:13:27 +0200 Subject: [PATCH 12/33] add db migration --- .../requests/OnSessionRequestUseCase.kt | 4 ++++ .../sign/src/main/sqldelight/databases/15.db | Bin 0 -> 61440 bytes 2 files changed, 4 insertions(+) create mode 100644 protocol/sign/src/main/sqldelight/databases/15.db diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt index 1c8f8a7b4..3c2daa0e1 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt @@ -150,6 +150,10 @@ internal class OnSessionRequestUseCase( scope.launch { _events.emit(sessionRequestEvent) } } + override fun onSessionRequestJson(topic: String, sessionRequestJson: String) { + //Nothing + } + private fun emitSessionRequest( params: SignParams.SessionRequestParams, request: WCRequest, diff --git a/protocol/sign/src/main/sqldelight/databases/15.db b/protocol/sign/src/main/sqldelight/databases/15.db new file mode 100644 index 0000000000000000000000000000000000000000..eb0b6ff496d0fe9530ee6b3a56d74ff58b2d322a GIT binary patch literal 61440 zcmeI(Z%^As9Ki9or3twF>66M&k0Ml}(y6FId$?w+ix?qL8m6I2vs~v|EP`!p)0KyX zs?zo*DouSOd%m}@_pygNJ3xGyLnWS6^1hK0>^s}J`}}@)w&n2RdD~B=cpb)vZYmzv z?$ivU_C$zUtv0FNbLxHZ%GM_?R@7gkusm*Yvi8l}-)H9jsm)G&s?Gf}_vh^0=|5)v zn%*}5o^DM2WPWb^R{uQlsUD6y0ObfE@V^x}+ni}w^Yg|>nYyp~@`X$iKMbC_;c#j8 zhaJ1swMDn}ecKkp@+DIU;rGOPr)#g-J7RlheY3T*CpPRo(c0~9t#{Opn|7xwI$P?! z+iovsd!%9Hd!lRq+!ec>_2;{`ULCo9><4eU!>zQv<-5p_PcFYxtBYmdJ#nHqOhYg1 z55A&ggPxnZd1c-lFAUN+?Du8t9LSSlc_RC-oyd(--}56kNJ|bENww4HCiy}A#BsfY zApEs2dvD~BNNbI1Xe8C~WitHMGNIqIu(kK-PvVZQDRgXgRam*?KUnr>LOZJe&d~Z)WzUbLH!?)TQ~OzKx7`)@?&WRQM{ZtpMOL3j+9_uDWlPNF>BdyU zT3j?f?B&7iD9j{sJ*lEuTsF0E7HjeZyvFP$lEDmFOaU$I;$jxLY;E3E;qANrd6WhP zuIGivL0T3#aVXRMuvc0o->RWmT2qpoddmjW?b5IZ%UNTaX7gLKZfz`%n>31S&Ps9Z z_UZjb!&+T6&W^M(Rov!c3>C{JwXl?JF&J)NH9psh!C2z8$-4D&bsTXSfwNLpI<;M| zTMr%>gS0iOR?KgRSB0R#|0 z009ILKmY**5I_I{?*GXF1Q0*~0R#|0009ILKmY**sxQFr|EnKkdWZl52q1s}0tg_0 z00IagfB?_`$pHiqKmY**5I_I{1Q0*~0R*Zq!1Mp=$Cw@>fB*srAbI-oHU;P- Date: Thu, 4 Sep 2025 13:53:27 +0200 Subject: [PATCH 13/33] reintegrate --- .gitignore | 1 + .../kotlin/com/reown/android/CoreProtocol.kt | 15 +------- .../reown/sign/engine/domain/SessionStore.kt | 37 +++++++++++++++---- .../reown/sign/engine/domain/SignEngine.kt | 1 + .../calls/RespondSessionRequestUseCase.kt | 9 +++-- .../requests/OnSessionRequestUseCase.kt | 32 ++++++++++++++-- .../com/reown/sample/common/ui/WCTopAppBar.kt | 2 +- .../sample/wallet/WalletKitApplication.kt | 3 +- 8 files changed, 71 insertions(+), 29 deletions(-) diff --git a/.gitignore b/.gitignore index 58fcf1bd6..c518bfc14 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt index 4ba55bcbb..9215799fb 100644 --- a/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt +++ b/core/android/src/main/kotlin/com/reown/android/CoreProtocol.kt @@ -120,7 +120,6 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter onError = onError, metaData = metaData, keyServerUrl = keyServerUrl, - signClient = signClient ) } catch (e: Exception) { onError(Core.Model.Error(e)) @@ -141,7 +140,6 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter try { require(projectId.isNotEmpty()) { "Project Id cannot be empty" } registerLogger(AndroidLogger()) - signClient = SignClient(projectId = projectId) setup( application = application, @@ -153,7 +151,6 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter onError = onError, metaData = metaData, keyServerUrl = keyServerUrl, - signClient = signClient ) } catch (e: Exception) { onError(Core.Model.Error(e)) @@ -171,7 +168,6 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter onError: (Core.Model.Error) -> Unit, metaData: Core.Model.AppMetaData, keyServerUrl: String?, - signClient: SignClient ) { val packageName: String = application.packageName val relayServerUrl = if (serverUrl.isNullOrEmpty()) "wss://relay.walletconnect.org?projectId=$projectId" else serverUrl @@ -179,24 +175,17 @@ class CoreProtocol(private val koinApp: KoinApplication = wcKoinApp) : CoreInter 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.SIGN_RUST_CLIENT)) { signClient } }, module { single(named(AndroidCommonDITags.TELEMETRY_ENABLED)) { TelemetryEnabled(telemetryEnabled) } }, coreAndroidNetworkModule(relayServerUrl, connectionType, BuildConfig.SDK_VERSION, networkClientTimeout, packageName), coreCommonModule(), coreCryptoModule(), ) - val (_, privateKey) = keyStore.getKeyPair() - scope.launch { - supervisorScope { - println("kobe: Setting key: $privateKey") - signClient.setKey(privateKey.hexToBytes()) - } - } - + signClient = SignClient(projectId = projectId, key = privateKey.hexToBytes()) if (relay == null) { Relay.initialize(connectionType) { error -> onError(Core.Model.Error(error)) } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt index aa8ebc278..c9df813f8 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SessionStore.kt @@ -8,15 +8,16 @@ import com.reown.sign.engine.model.mapper.toSessionFfi import com.reown.sign.engine.model.mapper.toVO import com.reown.sign.storage.sequence.SessionStorageRepository import com.reown.utils.isSequenceValid +import uniffi.yttrium.PairingFfi import uniffi.yttrium.SessionFfi -import uniffi.yttrium.SessionStore +import uniffi.yttrium.StorageFfi import kotlin.collections.filter internal class SessionStore( private val metadataStorageRepository: MetadataStorageRepositoryInterface, private val sessionStorageRepository: SessionStorageRepository, private val selfAppMetaData: AppMetaData -) : SessionStore { +) : StorageFfi { override fun addSession(session: SessionFfi) { println("kobe: SessionStore: addSession: $session") @@ -48,12 +49,6 @@ internal class SessionStore( .map { session -> session.toSessionFfi() } } - override fun deleteSession(topic: String) { - println("kobe: SessionStore: deleteSession: $topic") - - sessionStorageRepository.deleteSession(topic = Topic(topic)) - } - override fun getSession(topic: String): SessionFfi? { println("kobe: SessionStore: get session: $topic") @@ -63,4 +58,30 @@ internal class SessionStore( this.copy(peerAppMetaData = peerAppMetaData) }.toSessionFfi() } + + override fun savePairing(topic: String, rpcId: ULong, symKey: ByteArray, selfKey: ByteArray) { + TODO("Not yet implemented") + } + + override fun savePartialSession(topic: String, symKey: ByteArray) { + TODO("Not yet implemented") + } + + override fun getAllTopics(): List { + TODO("Not yet implemented") + } + + override fun getDecryptionKeyForTopic(topic: String): ByteArray? { + TODO("Not yet implemented") + } + + override fun getPairing(topic: String, rpcId: ULong): PairingFfi? { + TODO("Not yet implemented") + } + + override fun deleteSession(topic: String) { + println("kobe: SessionStore: deleteSession: $topic") + + sessionStorageRepository.deleteSession(topic = Topic(topic)) + } } diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt index 1996d3533..258e2f97f 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/domain/SignEngine.kt @@ -211,6 +211,7 @@ internal class SignEngine( scope.launch { signClient.registerSignListener(onSessionRequestUseCase) signClient.registerSessionStore(sessionStore) + signClient.start() } handleLinkModeRequests() diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/RespondSessionRequestUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/RespondSessionRequestUseCase.kt index dfd0e3f92..b55eb565d 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/RespondSessionRequestUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/calls/RespondSessionRequestUseCase.kt @@ -35,7 +35,9 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -import uniffi.yttrium.SessionRequestResponseJsonRpcFfi +import uniffi.yttrium.SessionRequestJsonRpcErrorResponseFfi +import uniffi.yttrium.SessionRequestJsonRpcResponseFfi +import uniffi.yttrium.SessionRequestJsonRpcResultResponseFfi import uniffi.yttrium.SignClient internal class RespondSessionRequestUseCase( @@ -60,7 +62,8 @@ internal class RespondSessionRequestUseCase( onFailure: (Throwable) -> Unit, ) = supervisorScope { try { - val responseFfi = SessionRequestResponseJsonRpcFfi( + //todo: add error response + val responseResultFfi = SessionRequestJsonRpcResultResponseFfi( id = jsonRpcResponse.id.toULong(), jsonrpc = "2.0", result = (jsonRpcResponse as JsonRpcResponse.JsonRpcResult).result.toString() @@ -68,7 +71,7 @@ internal class RespondSessionRequestUseCase( val result = async { try { - signClient.respond(topic, responseFfi) + signClient.respond(topic, SessionRequestJsonRpcResponseFfi.Result(responseResultFfi)) } catch (e: Exception) { println("kobe: session request error: $e") onFailure(e) diff --git a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt index 3c2daa0e1..6f4945793 100644 --- a/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt +++ b/protocol/sign/src/main/kotlin/com/reown/sign/engine/use_case/requests/OnSessionRequestUseCase.kt @@ -42,6 +42,8 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import uniffi.yttrium.SessionRequestJsonRpcFfi +import uniffi.yttrium.SessionRequestJsonRpcResponseFfi +import uniffi.yttrium.SettleNamespace import uniffi.yttrium.SignListener internal class OnSessionRequestUseCase( @@ -52,7 +54,7 @@ internal class OnSessionRequestUseCase( private val insertEventUseCase: InsertEventUseCase, private val clientId: String, private val logger: Logger -) : SignListener { +) : SignListener { //TODO: create global SignListener private val _events: MutableSharedFlow = MutableSharedFlow() val events: SharedFlow = _events.asSharedFlow() @@ -130,6 +132,22 @@ internal class OnSessionRequestUseCase( } } + override fun onSessionConnect(id: ULong) { + TODO("Not yet implemented") + } + + override fun onSessionDisconnect(id: ULong, topic: String) { + TODO("Not yet implemented") + } + + override fun onSessionEvent(id: ULong, topic: String, params: Boolean) { + TODO("Not yet implemented") + } + + override fun onSessionExtend(id: ULong, topic: String) { + TODO("Not yet implemented") + } + override fun onSessionRequest(topic: String, sessionRequest: SessionRequestJsonRpcFfi) { println("kobe: Session Request: $topic ; $sessionRequest") @@ -150,8 +168,16 @@ internal class OnSessionRequestUseCase( scope.launch { _events.emit(sessionRequestEvent) } } - override fun onSessionRequestJson(topic: String, sessionRequestJson: String) { - //Nothing + override fun onSessionRequestResponse(id: ULong, topic: String, response: SessionRequestJsonRpcResponseFfi) { + TODO("Not yet implemented") + } + + override fun onSessionUpdate( + id: ULong, + topic: String, + namespaces: Map + ) { + TODO("Not yet implemented") } private fun emitSessionRequest( diff --git a/sample/common/src/main/kotlin/com/reown/sample/common/ui/WCTopAppBar.kt b/sample/common/src/main/kotlin/com/reown/sample/common/ui/WCTopAppBar.kt index 1113055c1..d9f84f7c6 100644 --- a/sample/common/src/main/kotlin/com/reown/sample/common/ui/WCTopAppBar.kt +++ b/sample/common/src/main/kotlin/com/reown/sample/common/ui/WCTopAppBar.kt @@ -35,7 +35,7 @@ fun WCTopAppBar( Row( modifier = Modifier .fillMaxWidth() - .padding(start = 20.dp, top = 20.dp, end = 20.dp, bottom = 6.dp), + .padding(start = 20.dp, top = 60.dp, end = 20.dp, bottom = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { diff --git a/sample/wallet/src/main/kotlin/com/reown/sample/wallet/WalletKitApplication.kt b/sample/wallet/src/main/kotlin/com/reown/sample/wallet/WalletKitApplication.kt index 694a8126e..4966b150e 100644 --- a/sample/wallet/src/main/kotlin/com/reown/sample/wallet/WalletKitApplication.kt +++ b/sample/wallet/src/main/kotlin/com/reown/sample/wallet/WalletKitApplication.kt @@ -103,7 +103,8 @@ class WalletKitApplication : Application() { } mixPanel = MixpanelAPI.getInstance(this, CommonBuildConfig.MIX_PANEL, true).apply { - identify(CoreClient.Push.clientId) + println("kobe: UID: ${this@WalletKitApplication.applicationInfo.uid}") + identify(this@WalletKitApplication.applicationInfo.uid.toString()) people.set("\$name", EthAccountDelegate.ethAccount) } From 41847ce979467cbb1e2b0dc59e14cd04b6284e55 Mon Sep 17 00:00:00 2001 From: jakubuid Date: Mon, 8 Sep 2025 11:32:44 +0200 Subject: [PATCH 14/33] fix all the DI warnings --- .idea/codeStyles/Project.xml | 35 + core/android/build.gradle.kts | 1 + .../kotlin/com/reown/android/CoreProtocol.kt | 36 +- .../connection/DefaultConnectionLifecycle.kt | 15 +- .../internal/common/di/BaseStorageModule.kt | 1 + .../internal/common/di/CoreCryptoModule.kt | 3 - .../internal/common/di/CoreNetworkModule.kt | 2 +- .../clientid/ClientIdJwtRepositoryAndroid.kt | 42 - .../common/signing/eip6492/EIP6492Verifier.kt | 1 + .../android/pairing/client/PairingProtocol.kt | 10 +- .../pairing/engine/domain/PairingEngine.kt | 50 +- .../pairing/handler/PairingController.kt | 4 +- .../handler/PairingControllerInterface.kt | 4 - product/appkit/build.gradle.kts | 4 +- .../kotlin/com/reown/appkit/client/AppKit.kt | 11 +- product/walletkit/build.gradle.kts | 4 +- .../com/reown/walletkit/client/Wallet.kt | 19 +- .../com/reown/walletkit/client/WalletKit.kt | 60 +- protocol/sign/build.gradle.kts | 11 + .../com/reown/sign/client/GetKeyPair.kt | 55 ++ .../main/kotlin/com/reown/sign/client/Sign.kt | 17 +- .../com/reown/sign/client/SignListener.kt | 66 ++ .../com/reown/sign/client/SignProtocol.kt | 429 ++++++--- .../SessionStore.kt => client/SignStorage.kt} | 14 +- .../kotlin/com/reown/sign/di/CallsModule.kt | 91 +- .../kotlin/com/reown/sign/di/EngineModule.kt | 117 +-- .../reown/sign/engine/domain/SignEngine.kt | 817 +++++++++--------- .../ApproveSessionAuthenticateUseCase.kt | 132 +-- .../use_case/calls/ApproveSessionUseCase.kt | 4 +- .../calls/DisconnectSessionUseCase.kt | 26 +- .../engine/use_case/calls/EmitEventUseCase.kt | 22 +- .../use_case/calls/ExtendSessionUseCase.kt | 22 +- .../sign/engine/use_case/calls/PingUseCase.kt | 36 +- .../use_case/calls/ProposeSessionUseCase.kt | 24 +- .../calls/RejectSessionAuthenticateUseCase.kt | 70 +- .../use_case/calls/RejectSessionUseCase.kt | 36 +- .../calls/RespondSessionRequestUseCase.kt | 14 +- .../calls/SessionAuthenticateUseCase.kt | 120 +-- .../use_case/calls/SessionRequestUseCase.kt | 108 +-- .../use_case/calls/SessionUpdateUseCase.kt | 24 +- .../requests/OnSessionRequestUseCase.kt | 96 +- .../sample/wallet/WalletKitApplication.kt | 120 +-- .../sample/wallet/domain/NotifyDelegate.kt | 2 +- .../reown/sample/wallet/domain/WCDelegate.kt | 14 +- .../sample/wallet/ui/WalletKitActivity.kt | 26 +- .../sample/wallet/ui/Web3WalletViewModel.kt | 6 +- .../composable_routes/inbox/InboxViewModel.kt | 4 +- 47 files changed, 1544 insertions(+), 1281 deletions(-) create mode 100644 protocol/sign/src/main/kotlin/com/reown/sign/client/GetKeyPair.kt create mode 100644 protocol/sign/src/main/kotlin/com/reown/sign/client/SignListener.kt rename protocol/sign/src/main/kotlin/com/reown/sign/{engine/domain/SessionStore.kt => client/SignStorage.kt} (87%) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 4ce85a156..602469c89 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,6 +1,41 @@