diff --git a/Cargo.lock b/Cargo.lock index 47667aea98..42a5e0ae52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3550,6 +3550,7 @@ name = "kdf_walletconnect" version = "0.1.0" dependencies = [ "common", + "derive_more", "futures 0.3.28", "hex", "hkdf", @@ -3558,8 +3559,11 @@ dependencies = [ "rand 0.8.4", "relay_client", "relay_rpc", + "serde", + "serde_json", "sha2 0.10.8", "thiserror", + "wc_common", "x25519-dalek 2.0.1", ] diff --git a/mm2src/kdf_walletconnect/Cargo.toml b/mm2src/kdf_walletconnect/Cargo.toml index 211d655a3e..f8198bbb3c 100644 --- a/mm2src/kdf_walletconnect/Cargo.toml +++ b/mm2src/kdf_walletconnect/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] # chacha20poly1305 = "0.10" common = { path = "../common" } +derive_more = "0.99" futures = { version = "0.3", package = "futures", features = [ "compat", "async-await", @@ -21,4 +22,7 @@ relay_client = { path = "../../../kdf-wc/relay_client" } relay_rpc = { path = "../../../kdf-wc/relay_rpc" } sha2 = "0.10.8" thiserror = "1.0.40" +wc_common = { path = "../../../kdf-wc/wc_common" } x25519-dalek = { version = "2.0", features = ["static_secrets"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1", features = ["preserve_order", "raw_value"] } diff --git a/mm2src/kdf_walletconnect/src/error.rs b/mm2src/kdf_walletconnect/src/error.rs new file mode 100644 index 0000000000..67761fe678 --- /dev/null +++ b/mm2src/kdf_walletconnect/src/error.rs @@ -0,0 +1,25 @@ +use derive_more::Display; +use pairing_api::PairingClientError; +use relay_client::error::{ClientError, Error}; +use relay_rpc::rpc::PublishError; + +#[derive(Debug, Display)] +pub enum WalletConnectClientError { + PairingError(PairingClientError), + EncodeError(String), + PublishError(Error), + ClientError(ClientError), + PairingNotFound(String) +} + +impl From for WalletConnectClientError { + fn from(value: PairingClientError) -> Self { + WalletConnectClientError::PairingError(value) + } +} + +impl From for WalletConnectClientError { + fn from(value: ClientError) -> Self { + WalletConnectClientError::ClientError(value) + } +} diff --git a/mm2src/kdf_walletconnect/src/lib.rs b/mm2src/kdf_walletconnect/src/lib.rs index 823e4eae54..bc984785f3 100644 --- a/mm2src/kdf_walletconnect/src/lib.rs +++ b/mm2src/kdf_walletconnect/src/lib.rs @@ -1,16 +1,21 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, sync::Arc, time::Duration}; -use futures::{channel::mpsc::{unbounded, UnboundedReceiver}, lock::Mutex}; +use error::WalletConnectClientError; +use futures::{channel::mpsc::{unbounded, UnboundedReceiver}, lock::Mutex, StreamExt}; use handler::Handler; use mm2_err_handle::prelude::MmResult; -use pairing_api::PairingClient; -use relay_client::{error::ClientError, websocket::{Client, PublishedMessage}, ConnectionOptions}; -use relay_rpc::domain::{SubscriptionId, Topic}; +use pairing_api::{Methods, PairingClient}; +use relay_client::{websocket::{Client, PublishedMessage}, ConnectionOptions, MessageIdGenerator}; +use relay_rpc::{domain::{MessageId, SubscriptionId, Topic}, rpc::{params::{IrnMetadata, Metadata}, Params, Payload, Request, Response, SuccessfulResponse, JSON_RPC_VERSION_STR}}; use session_key::SessionKey; use mm2_err_handle::prelude::*; +use wc_common::{encrypt_and_encode, EnvelopeType}; +use common::log::info; + mod session_key; mod handler; +mod error; pub const RELAY_ADDRESS: &str = "wss://relay.walletconnect.com"; pub const PROJECT_ID: &str = "86e916bcbacee7f98225dde86b697f5b"; @@ -39,22 +44,120 @@ impl Default for WalletConnectClient { impl WalletConnectClient { pub fn new() -> Self { - let (sender, receiver) = unbounded(); + let (msg_sender, msg_receiver) = unbounded(); let pairing = PairingClient::new(); - let client = Arc::new(Client::new(Handler::new("Komodefi", sender))); + let client = Arc::new(Client::new(Handler::new("Komodefi", msg_sender))); Self { client, pairing, sessions: HashMap::new(), - handler: Arc::new(Mutex::new(receiver)) + handler: Arc::new(Mutex::new(msg_receiver)) } } - pub async fn connect(&self, opts: &ConnectionOptions) -> MmResult<(), ClientError>{ - self.client.connect(opts).await.map_to_mm(|err|err) + pub async fn create_pairing(&self, metadata: Metadata, methods: Option) -> MmResult<(Topic, String), WalletConnectClientError> { + Ok(self.pairing.create(metadata, methods, &self.client).await?) + } + + pub async fn connect_to_pairing(&self, url: &str, activate: bool) -> MmResult { + Ok(self.pairing.pair(url, activate, &self.client).await?) + } + + pub async fn connect(&self, opts: &ConnectionOptions) -> MmResult<(), WalletConnectClientError> { + Ok(self.client.connect(opts).await?) } + /// Private function to publish a request. + async fn publish_request( + &self, + topic: &str, + params: Params, + irn_metadata: IrnMetadata + ) -> MmResult<(), WalletConnectClientError> { + let message_id = MessageIdGenerator::new().next(); + let request = Request::new(message_id, params); + // Publish the encrypted message + self.publish_payload(topic, irn_metadata, Payload::Request(request)) + .await?; + + info!("Otbound request sent!\n"); + + Ok(()) + } + + /// Private function to publish a request response. + async fn publish_response( + &self, + topic: &str, + params: Params, + irn_metadata: IrnMetadata, + message_id: MessageId, + ) -> MmResult<(), WalletConnectClientError> { + let response = Response::Success(SuccessfulResponse { + id: message_id, + jsonrpc: JSON_RPC_VERSION_STR.into(), + result: serde_json::to_value(params) + .map_to_mm(|err| WalletConnectClientError::EncodeError(err.to_string()))?, + }); + + // Publish the encrypted message + self.publish_payload(topic, irn_metadata, Payload::Response(response)) + .await?; + + println!("\nOutbound request sent!"); + + Ok(()) + } + + + /// Private function to publish a payload. + async fn publish_payload( + &self, + topic: &str, + irn_metadata: IrnMetadata, + payload: Payload, + ) -> MmResult<(), WalletConnectClientError> { + // try to extend session before updating local store. + let sym_key = { + let pairings = self.pairing.pairings.lock().await; + let pairing = pairings + .get(topic) + .ok_or_else(|| WalletConnectClientError::PairingNotFound(format!("Pariring not found for topic:{topic}")))?; + hex::decode(pairing.sym_key.clone()).map_to_mm(|err| { + WalletConnectClientError::EncodeError(format!("Failed to decode sym_key: {:?}", err)) + })? + }; + + let payload = serde_json::to_string(&payload) + .map_to_mm(|err| WalletConnectClientError::EncodeError(err.to_string()))?; + let message = encrypt_and_encode(EnvelopeType::Type0, payload, &sym_key) + .map_to_mm(|err| WalletConnectClientError::EncodeError(err.to_string()))?; + + // Publish the encrypted message + { + self.client + .publish( + topic.into(), + message, + None, + irn_metadata.tag, + Duration::from_secs(irn_metadata.ttl), + irn_metadata.prompt, + ) + .await + .map_to_mm(WalletConnectClientError::PublishError)?; + }; + + Ok(()) + } +} + +pub async fn published_message_event_loop(client: Arc) { + let mut recv = client.handler.lock().await; + while let Some(data) = recv.next().await { + todo!() + } }