Skip to content

Commit

Permalink
save dev state - implement dynamic session management for activated c…
Browse files Browse the repository at this point in the history
…oins
  • Loading branch information
borngraced committed Dec 31, 2024
1 parent f95e385 commit dd1a842
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 34 deletions.
1 change: 1 addition & 0 deletions mm2src/coins/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ pub enum EthPrivKeyBuildPolicy {
WalletConnect {
address: Address,
public_key_uncompressed: H520,
session_topic: String,
},
}

Expand Down
2 changes: 1 addition & 1 deletion mm2src/coins/eth/eth_withdraw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,9 @@ where
},
EthPrivKeyPolicy::WalletConnect { .. } => {
let ctx = MmArc::from_weak(&coin.ctx).expect("No context");

let wc = WalletConnectCtx::from_ctx(&ctx)
.expect("TODO: handle error when enable kdf initialization without key.");

let gas_price = pay_for_gas_option.get_gas_price();
let (nonce, _) = coin
.clone()
Expand Down
6 changes: 5 additions & 1 deletion mm2src/coins/eth/v2_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,9 @@ pub enum EthPrivKeyActivationPolicy {
Trezor,
#[cfg(target_arch = "wasm32")]
Metamask,
WalletConnect,
WalletConnect {
session_topic: String,
},
}

impl EthPrivKeyActivationPolicy {
Expand Down Expand Up @@ -825,12 +827,14 @@ pub(crate) async fn build_address_and_priv_key_policy(
EthPrivKeyBuildPolicy::WalletConnect {
address,
public_key_uncompressed,
session_topic,
} => {
let public_key = compress_public_key(public_key_uncompressed)?;
Ok((
EthPrivKeyPolicy::WalletConnect {
public_key,
public_key_uncompressed,
session_topic,
},
DerivationMethod::SingleAddress(address),
))
Expand Down
67 changes: 54 additions & 13 deletions mm2src/coins/eth/wallet_connect.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// https://docs.reown.com/advanced/multichain/rpc-reference/ethereum-rpc
use crate::common::Future01CompatExt;
use crate::Eip1559Ops;
use crate::{BytesJson, TransactionErr};
use crate::{BytesJson, MarketCoinOps, TransactionErr};

use common::log::info;
use derive_more::Display;
Expand All @@ -21,7 +21,7 @@ use secp256k1::{recovery::{RecoverableSignature, RecoveryId},
use std::str::FromStr;
use web3::signing::hash_message;

use super::EthCoin;
use super::{EthCoin, EthPrivKeyPolicy};

// Wait for 60 seconds for the transaction to appear on the RPC node.
const WAIT_RPC_TIMEOUT_SECS: u64 = 60;
Expand Down Expand Up @@ -90,13 +90,14 @@ impl<'a> WcEthTxParams<'a> {
#[async_trait::async_trait]
impl WalletConnectOps for EthCoin {
type Error = MmError<EthWalletConnectError>;
type Params<'a> = WcEthTxParams<'a>;
type SignTxData = (SignedTransaction, BytesJson);
type SendTxData = (SignedTransaction, BytesJson);
type Params<'a> = WcEthTxParams<'a>;

async fn wc_chain_id(&self, wc: &WalletConnectCtx) -> Result<WcChainId, Self::Error> {
let chain_id = WcChainId::new_eip155(self.chain_id.to_string());
wc.validate_update_active_chain_id(&chain_id).await?;
let session_topic = self.session_topic(wc).await?;
wc.validate_update_active_chain_id(session_topic, &chain_id).await?;

Ok(chain_id)
}
Expand All @@ -109,8 +110,15 @@ impl WalletConnectOps for EthCoin {
let bytes = {
let chain_id = self.wc_chain_id(wc).await?;
let tx_json = params.prepare_wc_tx_format()?;
let session_topic = self.session_topic(wc).await?;
let tx_hex: String = wc
.send_session_request_and_wait(&chain_id, WcRequestMethods::EthSignTransaction, tx_json, Ok)
.send_session_request_and_wait(
session_topic,
&chain_id,
WcRequestMethods::EthSignTransaction,
tx_json,
Ok,
)
.await?;
// First 4 bytes from WalletConnect represents Protoc info
hex::decode(&tx_hex[4..])?
Expand All @@ -130,8 +138,15 @@ impl WalletConnectOps for EthCoin {
let tx_hash: String = {
let chain_id = self.wc_chain_id(wc).await?;
let tx_json = params.prepare_wc_tx_format()?;
wc.send_session_request_and_wait(&chain_id, WcRequestMethods::EthSendTransaction, tx_json, Ok)
.await?
let session_topic = self.session_topic(wc).await?;
wc.send_session_request_and_wait(
session_topic,
&chain_id,
WcRequestMethods::EthSendTransaction,
tx_json,
Ok,
)
.await?
};

let tx_hash = tx_hash.strip_prefix("0x").unwrap_or(&tx_hash);
Expand All @@ -153,26 +168,52 @@ impl WalletConnectOps for EthCoin {

Ok((signed_tx, tx_hex))
}

async fn session_topic(&self, wc: &WalletConnectCtx) -> Result<&str, Self::Error> {
if let EthPrivKeyPolicy::WalletConnect { ref session_topic, .. } = &self.priv_key_policy {
let topic = session_topic.as_str().into();
if wc.session_manager.get_session(&topic).is_some() {
return Ok(session_topic);
}

return MmError::err(EthWalletConnectError::InternalError(format!(
"session topic:{session_topic} for {}, is not activated or has expired",
self.ticker()
)));
}

MmError::err(EthWalletConnectError::InternalError(format!(
"{} is not activated via WalletConnect",
self.ticker()
)))
}
}

pub async fn eth_request_wc_personal_sign(
wc: &WalletConnectCtx,
session_topic: &str,
chain_id: u64,
) -> MmResult<(H520, Address), EthWalletConnectError> {
let chain_id = WcChainId::new_eip155(chain_id.to_string());
wc.validate_update_active_chain_id(&chain_id).await?;
wc.validate_update_active_chain_id(session_topic, &chain_id).await?;

let account_str = wc.get_account_for_chain_id(&chain_id)?;
let account_str = wc.get_account_for_chain_id(session_topic, &chain_id)?;
let message = "Authenticate with Komodefi";
let params = {
let message_hex = format!("0x{}", hex::encode(message));
json!(&[&message_hex, &account_str])
};
let data = wc
.send_session_request_and_wait(&chain_id, WcRequestMethods::PersonalSign, params, |data: String| {
extract_pubkey_from_signature(&data, message, &account_str)
.mm_err(|err| WalletConnectError::SessionError(err.to_string()))
})
.send_session_request_and_wait(
session_topic,
&chain_id,
WcRequestMethods::PersonalSign,
params,
|data: String| {
extract_pubkey_from_signature(&data, message, &account_str)
.mm_err(|err| WalletConnectError::SessionError(err.to_string()))
},
)
.await?;

Ok(data)
Expand Down
2 changes: 2 additions & 0 deletions mm2src/coins/lp_coins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3914,9 +3914,11 @@ pub enum PrivKeyPolicy<T> {
/// public keys.
/// - `public_key`: Compressed public key, represented as [H264].
/// - `public_key_uncompressed`: Uncompressed public key, represented as [H520].
/// - `session_topic`: WalletConnect session that was used to activate this coin.
WalletConnect {
public_key: H264,
public_key_uncompressed: H520,
session_topic: String,
},
}

Expand Down
34 changes: 30 additions & 4 deletions mm2src/coins/tendermint/wallet_connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ impl WalletConnectOps for TendermintCoin {

async fn wc_chain_id(&self, wc: &WalletConnectCtx) -> Result<WcChainId, Self::Error> {
let chain_id = WcChainId::new_cosmos(self.chain_id.to_string());
wc.validate_update_active_chain_id(&chain_id).await?;
let session_topic = self.session_topic(wc).await?;
wc.validate_update_active_chain_id(session_topic, &chain_id).await?;
Ok(chain_id)
}

Expand All @@ -97,7 +98,11 @@ impl WalletConnectOps for TendermintCoin {
WcRequestMethods::CosmosSignDirect
};

wc.send_session_request_and_wait(&chain_id, method, params, |data: CosmosTxSignedData| {
let session_topic = self
.session_topic(wc)
.await
.expect("TODO: handle after updating tendermint coin init");
wc.send_session_request_and_wait(session_topic, &chain_id, method, params, |data: CosmosTxSignedData| {
let signature = general_purpose::STANDARD
.decode(data.signature.signature)
.map_to_mm(|err| WalletConnectError::PayloadError(err.to_string()))?;
Expand All @@ -118,16 +123,36 @@ impl WalletConnectOps for TendermintCoin {
) -> Result<Self::SendTxData, Self::Error> {
todo!()
}

async fn session_topic(&self, _wc: &WalletConnectCtx) -> Result<&str, Self::Error> {
// if let EthPrivKeyPolicy::WalletConnect { ref session_topic, .. } = &self.priv_key_policy {
// if wc.session_manager.get_session(&(session_topic.into())).is_some() {
// return Ok(session_topic);
// }
//
// return MmError::err(EthWalletConnectError::InternalError(format!(
// "session topic:{session_topic} for {}, is not activated or has expired",
// self.ticker()
// )));
// }
//
// MmError::err(EthWalletConnectError::InternalError(format!(
// "{} is not activated via WalletConnect",
// self.ticker()
// )))
todo!()
}
}

pub async fn cosmos_get_accounts_impl(
wc: &WalletConnectCtx,
session_topic: &str,
chain_id: &str,
) -> MmResult<CosmosAccount, WalletConnectError> {
let chain_id = WcChainId::new_cosmos(chain_id.to_string());
wc.validate_update_active_chain_id(&chain_id).await?;
wc.validate_update_active_chain_id(session_topic, &chain_id).await?;

let account = wc.get_account_for_chain_id(&chain_id)?;
let account = wc.get_account_for_chain_id(session_topic, &chain_id)?;
let session = wc
.session_manager
.get_session_active()
Expand Down Expand Up @@ -160,6 +185,7 @@ pub async fn cosmos_get_accounts_impl(

let params = serde_json::to_value(&account).unwrap();
wc.send_session_request_and_wait(
session_topic,
&chain_id,
WcRequestMethods::CosmosGetAccounts,
params,
Expand Down
10 changes: 6 additions & 4 deletions mm2src/coins_activation/src/eth_with_token_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,17 +487,19 @@ async fn eth_priv_key_build_policy(
Ok(EthPrivKeyBuildPolicy::Metamask(metamask_ctx))
},
EthPrivKeyActivationPolicy::Trezor => Ok(EthPrivKeyBuildPolicy::Trezor),
EthPrivKeyActivationPolicy::WalletConnect => {
EthPrivKeyActivationPolicy::WalletConnect { session_topic } => {
let wc = WalletConnectCtx::from_ctx(ctx)
.expect("TODO: handle error when enable kdf initialization without key.");
let chain_id = conf["chain_id"].as_u64().ok_or(EthActivationV2Error::ChainIdNotSet)?;
let (public_key_uncompressed, address) = eth_request_wc_personal_sign(&wc, chain_id)
.await
.mm_err(|err| EthActivationV2Error::WalletConnectError(err.to_string()))?;
let (public_key_uncompressed, address) =
eth_request_wc_personal_sign(&wc, session_topic, chain_id)
.await
.mm_err(|err| EthActivationV2Error::WalletConnectError(err.to_string()))?;

Ok(EthPrivKeyBuildPolicy::WalletConnect {
address,
public_key_uncompressed,
session_topic: session_topic.clone(),
})
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,11 +231,12 @@ impl From<TendermintInitError> for EnablePlatformCoinWithTokensError {

async fn activate_with_walletconnect(
ctx: &MmArc,
session_topic: &str,
chain_id: &str,
ticker: &str,
) -> MmResult<(TendermintActivationPolicy, TendermintWalletConnectionType), TendermintInitError> {
let wc = WalletConnectCtx::from_ctx(ctx).expect("TODO: handle error when enable kdf initialization without key.");
let account = cosmos_get_accounts_impl(&wc, chain_id)
let account = cosmos_get_accounts_impl(&wc, session_topic, chain_id)
.await
.mm_err(|err| TendermintInitError {
ticker: ticker.to_string(),
Expand Down Expand Up @@ -304,7 +305,9 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin {
)
},
TendermintPubkeyActivationParams::WalletConnect => {
activate_with_walletconnect(&ctx, protocol_conf.chain_id.as_ref(), &ticker).await?
//TODO: uncomment when dynamic session topic is implemented for tendermint.
let session_topic = "TOPIC".to_owned();
activate_with_walletconnect(&ctx, &session_topic, protocol_conf.chain_id.as_ref(), &ticker).await?
},
}
} else {
Expand Down
29 changes: 21 additions & 8 deletions mm2src/kdf_walletconnect/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,17 @@ pub trait WalletConnectOps {

async fn wc_sign_tx<'a>(
&self,
ctx: &WalletConnectCtx,
wc: &WalletConnectCtx,
params: Self::Params<'a>,
) -> Result<Self::SignTxData, Self::Error>;

async fn wc_send_tx<'a>(
&self,
ctx: &WalletConnectCtx,
wc: &WalletConnectCtx,
params: Self::Params<'a>,
) -> Result<Self::SendTxData, Self::Error>;

async fn session_topic(&self, wc: &WalletConnectCtx) -> Result<&str, Self::Error>;
}

pub struct WalletConnectCtxImpl {
Expand Down Expand Up @@ -492,10 +494,15 @@ impl WalletConnectCtxImpl {
MmError::err(WalletConnectError::ChainIdNotSupported(chain_id.to_string()))
}

pub async fn validate_update_active_chain_id(&self, chain_id: &WcChainId) -> MmResult<(), WalletConnectError> {
pub async fn validate_update_active_chain_id(
&self,
session_topic: &str,
chain_id: &WcChainId,
) -> MmResult<(), WalletConnectError> {
let session_topic = session_topic.into();
let session =
self.session_manager
.get_session_active()
.get_session(&session_topic)
.ok_or(MmError::new(WalletConnectError::SessionError(
"No active WalletConnect session found".to_string(),
)))?;
Expand Down Expand Up @@ -547,10 +554,15 @@ impl WalletConnectCtxImpl {
}

/// Retrieves the available account for a given chain ID.
pub fn get_account_for_chain_id(&self, chain_id: &WcChainId) -> MmResult<String, WalletConnectError> {
pub fn get_account_for_chain_id(
&self,
session_topic: &str,
chain_id: &WcChainId,
) -> MmResult<String, WalletConnectError> {
let session_topic = session_topic.into();
let namespaces = &self
.session_manager
.get_session_active()
.get_session(&session_topic)
.ok_or(MmError::new(WalletConnectError::SessionError(
"No active WalletConnect session found".to_string(),
)))?
Expand All @@ -573,6 +585,7 @@ impl WalletConnectCtxImpl {
/// https://specs.walletconnect.com/2.0/specs/clients/sign/session-events#session_request
pub async fn send_session_request_and_wait<T, R, F>(
&self,
session_topic: &str,
chain_id: &WcChainId,
method: WcRequestMethods,
params: serde_json::Value,
Expand All @@ -582,7 +595,7 @@ impl WalletConnectCtxImpl {
T: DeserializeOwned,
F: Fn(T) -> MmResult<R, WalletConnectError>,
{
let active_topic = self.session_manager.get_active_topic_or_err()?;
let session_topic = session_topic.into();
let request = SessionRequestRequest {
chain_id: chain_id.to_string(),
request: SessionRequest {
Expand All @@ -592,7 +605,7 @@ impl WalletConnectCtxImpl {
},
};
let request = RequestParams::SessionRequest(request);
let (rx, ttl) = self.publish_request(&active_topic, request).await?;
let (rx, ttl) = self.publish_request(&session_topic, request).await?;

let response = rx
.timeout(ttl)
Expand Down
2 changes: 1 addition & 1 deletion mm2src/kdf_walletconnect/src/session/rpc/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ pub async fn handle_session_event(
// TODO: Handle accountsChanged event logic.
},
_ => {
// TODO: Handle other event logic.},
// TODO: Handle other event logic.,
},
};

Expand Down

0 comments on commit dd1a842

Please sign in to comment.