diff --git a/crates/rust-sample-wallet/src/app.rs b/crates/rust-sample-wallet/src/app.rs index a6490c5a..dd0205ae 100644 --- a/crates/rust-sample-wallet/src/app.rs +++ b/crates/rust-sample-wallet/src/app.rs @@ -574,9 +574,10 @@ pub fn App() -> impl IntoView { "session delete on topic: {id}: {topic}", ); } - IncomingSessionMessage::SessionEvent(id, topic, params) => { + IncomingSessionMessage::SessionEvent(topic, name, data, chain_id) => { tracing::info!( - "session event on topic: {id}: {topic}: {params:?}", + "session event on topic: {topic}: name={name}, chainId={chain_id}, data={:?}", + data ); } IncomingSessionMessage::SessionUpdate(id, topic, params) => { diff --git a/crates/yttrium/src/sign/client.rs b/crates/yttrium/src/sign/client.rs index de77ebf7..c5cef886 100644 --- a/crates/yttrium/src/sign/client.rs +++ b/crates/yttrium/src/sign/client.rs @@ -1,8 +1,9 @@ use { crate::sign::{ client_errors::{ - ApproveError, ConnectError, DisconnectError, ExtendError, - PairError, RejectError, RequestError, RespondError, UpdateError, + ApproveError, ConnectError, DisconnectError, EmitError, + ExtendError, PairError, RejectError, RequestError, RespondError, + UpdateError, }, client_types::{ ConnectParams, ConnectResult, PairingInfo, RejectionReason, @@ -804,10 +805,57 @@ impl Client { unimplemented!() } - pub async fn _emit(&self) { - // TODO implement - // https://github.com/WalletConnect/walletconnect-monorepo/blob/5bef698dcf0ae910548481959a6a5d87eaf7aaa5/packages/sign-client/src/controllers/engine.ts#L764 - unimplemented!() + pub async fn emit( + &mut self, + topic: Topic, + name: String, + data: serde_json::Value, + chain_id: String, + ) -> Result<(), EmitError> { + let shared_secret = self + .session_store + .get_session(topic.clone()) + .map_err(EmitError::Storage)? + .map(|s| s.session_sym_key) + .ok_or(EmitError::SessionNotFound)?; + + let id = generate_rpc_id(); + let message = serialize_and_encrypt_message_type0_envelope( + shared_secret, + &crate::sign::protocol_types::SessionEventJsonRpc { + id, + jsonrpc: "2.0".to_string(), + method: "wc_sessionEvent".to_string(), + params: crate::sign::protocol_types::EventParams { + event: crate::sign::protocol_types::SessionEventVO { + name, + data, + }, + chain_id, + }, + }, + ) + .map_err(EmitError::ShouldNeverHappen)?; + + self.do_request::(relay_rpc::rpc::Params::Publish(Publish { + topic, + message, + attestation: None, + ttl_secs: 86400, + tag: 1110, + prompt: false, + analytics: Some(AnalyticsData { + correlation_id: Some(id.try_into().unwrap()), + chain_id: None, + rpc_methods: None, + tx_hashes: None, + contract_addresses: None, + }), + })) + .await + .map_err(EmitError::Request)?; + + Ok(()) } pub async fn disconnect( diff --git a/crates/yttrium/src/sign/client_errors.rs b/crates/yttrium/src/sign/client_errors.rs index 7f931080..cf7759ec 100644 --- a/crates/yttrium/src/sign/client_errors.rs +++ b/crates/yttrium/src/sign/client_errors.rs @@ -106,6 +106,23 @@ pub enum DisconnectError { Request(RequestError), } +#[derive(Debug, thiserror::Error)] +#[cfg_attr(feature = "uniffi", derive(uniffi_macros::Error))] +#[error("Sign emit error: {0}")] +pub enum EmitError { + #[error("Storage: {0}")] + Storage(StorageError), + + #[error("Session not found")] + SessionNotFound, + + #[error("Request: {0}")] + Request(RequestError), + + #[error("Should never happen: {0}")] + ShouldNeverHappen(String), +} + #[derive(Debug, thiserror::Error)] #[cfg_attr(feature = "uniffi", derive(uniffi_macros::Error))] #[error("Sign connect error: {0}")] diff --git a/crates/yttrium/src/sign/incoming.rs b/crates/yttrium/src/sign/incoming.rs index d9ac291d..a5540dfd 100644 --- a/crates/yttrium/src/sign/incoming.rs +++ b/crates/yttrium/src/sign/incoming.rs @@ -258,16 +258,31 @@ pub fn handle( ); } Ok(()) - } else if method.as_str() == Some("wc_sessionEmit") { + } else if method.as_str() == Some("wc_sessionEvent") { // TODO dedup events based on JSON RPC history - // TODO emit event callback (blocking?) + // Parse wc_sessionEvent params + let params = + serde_json::from_value::< + crate::sign::protocol_types::EventParams, + >(value.get("params").cloned().ok_or_else( + || HandleError::Client("params not found".to_string()), + )?) + .map_err(|e| { + HandleError::Client(format!("parse event params: {e}")) + })?; + + let name = params.event.name; + let data_value = params.event.data; + let chain_id = params.chain_id; + session_request_tx .send(( sub_msg.data.topic.clone(), IncomingSessionMessage::SessionEvent( - 0, // TODO sub_msg.data.topic, - false, // TODO + name, + data_value, + chain_id, ), )) .map_err(|e| { diff --git a/crates/yttrium/src/sign/protocol_types.rs b/crates/yttrium/src/sign/protocol_types.rs index b751ff19..a4f1bb79 100644 --- a/crates/yttrium/src/sign/protocol_types.rs +++ b/crates/yttrium/src/sign/protocol_types.rs @@ -247,3 +247,26 @@ pub struct SessionExtendJsonRpc { pub method: String, pub params: SessionExtend, } + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SessionEventVO { + pub name: String, + pub data: serde_json::Value, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct EventParams { + pub event: SessionEventVO, + pub chain_id: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SessionEventJsonRpc { + pub id: u64, + pub jsonrpc: String, + pub method: String, + pub params: EventParams, +} diff --git a/crates/yttrium/src/sign/relay.rs b/crates/yttrium/src/sign/relay.rs index fbac1352..6b5c8134 100644 --- a/crates/yttrium/src/sign/relay.rs +++ b/crates/yttrium/src/sign/relay.rs @@ -597,7 +597,7 @@ enum ConnectionState { pub enum IncomingSessionMessage { SessionRequest(SessionRequestJsonRpc), Disconnect(u64, Topic), - SessionEvent(u64, Topic, bool), + SessionEvent(Topic, String, serde_json::Value, String), SessionUpdate(u64, Topic, crate::sign::protocol_types::SettleNamespaces), SessionExtend(u64, Topic), SessionConnect(u64, Topic), diff --git a/crates/yttrium/src/uniffi_compat/sign/client.rs b/crates/yttrium/src/uniffi_compat/sign/client.rs index f7c3044e..cf4f8b90 100644 --- a/crates/yttrium/src/uniffi_compat/sign/client.rs +++ b/crates/yttrium/src/uniffi_compat/sign/client.rs @@ -3,9 +3,9 @@ use { sign::{ client::{generate_client_id_key, Client}, client_errors::{ - ApproveError, ConnectError, DisconnectError, ExtendError, - PairError, RejectError, RequestError, RespondError, - UpdateError, + ApproveError, ConnectError, DisconnectError, EmitError, + ExtendError, PairError, RejectError, RequestError, + RespondError, UpdateError, }, client_types::{ConnectParams, SessionProposal}, protocol_types::{Metadata, SessionRequest, SettleNamespace}, @@ -33,7 +33,13 @@ pub trait SignListener: Send + Sync { ); fn on_session_disconnect(&self, id: u64, topic: String); - fn on_session_event(&self, id: u64, topic: String, params: bool); + fn on_session_event( + &self, + topic: String, + name: String, + data: String, + chain_id: String, + ); fn on_session_extend(&self, id: u64, topic: String); fn on_session_update( &self, @@ -121,14 +127,17 @@ impl SignClient { .on_session_disconnect(id, topic.to_string()); } IncomingSessionMessage::SessionEvent( - id, topic, - params, + name, + data, + chain_id, ) => { listener.on_session_event( - id, topic.to_string(), - params, + name, + serde_json::to_string(&data) + .unwrap_or_default(), + chain_id, ); } IncomingSessionMessage::SessionUpdate( @@ -249,6 +258,22 @@ impl SignClient { Ok(topic) } + pub async fn emit( + &self, + topic: String, + name: String, + data: String, + chain_id: String, + ) -> Result<(), EmitError> { + let mut client = self.client.lock().await; + let data_value = match serde_json::from_str::(&data) + { + Ok(v) => v, + Err(_) => serde_json::Value::String(data.clone()), + }; + client.emit(topic.into(), name, data_value, chain_id).await + } + pub async fn disconnect( &self, topic: String,