Skip to content

Commit

Permalink
implement coin activation for tendermint and other minor changes
Browse files Browse the repository at this point in the history
  • Loading branch information
borngraced committed Sep 21, 2024
1 parent 2d68488 commit 42dd5ba
Show file tree
Hide file tree
Showing 12 changed files with 5,216 additions and 59 deletions.
5 changes: 5 additions & 0 deletions mm2src/coins/tendermint/tendermint_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,11 @@ pub enum TendermintInitErrorKind {
BalanceStreamInitError(String),
#[display(fmt = "Watcher features can not be used with pubkey-only activation policy.")]
CantUseWatchersWithPubkeyPolicy,
#[display(
fmt = "Unable to fetch chain account from WalletConnect. Please try again or reconnect your session - {}",
_0
)]
UnableToFetchChainAccount(String),
}

#[derive(Display, Debug, Serialize, SerializeErrorType)]
Expand Down
80 changes: 71 additions & 9 deletions mm2src/coins_activation/src/tendermint_with_assets_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use coins::tendermint::{tendermint_priv_key_policy, RpcNode, TendermintActivatio
use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum, PrivKeyBuildPolicy};
use common::executor::{AbortSettings, SpawnAbortable};
use common::{true_f, Future01CompatExt};
use kdf_walletconnect::chain::cosmos::CosmosAccountAlgo;
use mm2_core::mm_ctx::MmArc;
use mm2_err_handle::prelude::*;
use mm2_event_stream::behaviour::{EventBehaviour, EventInitStatus};
Expand All @@ -38,6 +39,14 @@ impl RegisterTokenInfo<TendermintToken> for TendermintCoin {
}
}

#[derive(Clone, Default, Deserialize)]
pub struct WalletConnectParams {
#[serde(default)]
pub account_index: u8,
#[serde(default)]
pub enabled: bool,
}

#[derive(Clone, Deserialize)]
pub struct TendermintActivationParams {
nodes: Vec<RpcNode>,
Expand All @@ -55,7 +64,7 @@ pub struct TendermintActivationParams {
#[serde(default)]
is_keplr_from_ledger: bool,
#[serde(default)]
is_walletconnect: bool,
walletconnect: WalletConnectParams,
}

fn deserialize_account_public_key<'de, D>(deserializer: D) -> Result<Option<TendermintPublicKey>, D::Error>
Expand Down Expand Up @@ -219,6 +228,41 @@ impl From<TendermintInitError> for EnablePlatformCoinWithTokensError {
}
}

async fn get_walletconnect_pubkey(
ctx: &MmArc,
param: &WalletConnectParams,
chain_id: &str,
ticker: &str,
) -> MmResult<TendermintActivationPolicy, TendermintInitError> {
if ctx.is_watcher() || ctx.use_watchers() {
return MmError::err(TendermintInitError {
ticker: ticker.to_string(),
kind: TendermintInitErrorKind::CantUseWatchersWithPubkeyPolicy,
});
};

let account = ctx
.wallect_connect
.cosmos_get_account(param.account_index, "cosmos", chain_id)
.await
.mm_err(|err| TendermintInitError {
ticker: ticker.to_string(),
kind: TendermintInitErrorKind::UnableToFetchChainAccount(err.to_string()),
})?;

let pubkey = match account.algo {
CosmosAccountAlgo::Secp256k1 => {
TendermintPublicKey::from_raw_secp256k1(&account.pubkey).ok_or("Invalid secp256k1 pubkey".to_owned())
},
}
.map_to_mm(|e| TendermintInitError {
ticker: ticker.to_string(),
kind: TendermintInitErrorKind::Internal(e),
})?;

Ok(TendermintActivationPolicy::with_public_key(pubkey))
}

#[async_trait]
impl PlatformCoinWithTokensActivationOps for TendermintCoin {
type ActivationRequest = TendermintActivationParams;
Expand All @@ -238,17 +282,35 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin {
protocol_conf: Self::PlatformProtocolInfo,
) -> Result<Self, MmError<Self::ActivationError>> {
let conf = TendermintConf::try_from_json(&ticker, coin_conf)?;
let is_keplr_from_ledger = activation_request.is_keplr_from_ledger && activation_request.with_pubkey.is_some();

let activation_policy = if let Some(pubkey) = activation_request.with_pubkey {
if ctx.is_watcher() || ctx.use_watchers() {
return MmError::err(TendermintInitError {
ticker: ticker.clone(),
kind: TendermintInitErrorKind::CantUseWatchersWithPubkeyPolicy,
});
}
let use_with_pubkey = activation_request.with_pubkey.is_some();
let is_keplr_from_ledger = activation_request.is_keplr_from_ledger && use_with_pubkey;
let use_walletconnect = activation_request.walletconnect.enabled;

if use_walletconnect && use_with_pubkey {
return MmError::err(TendermintInitError {
ticker: ticker.clone(),
kind: TendermintInitErrorKind::Internal("Can't activate tendermint in pubkey and WalletConnect mode enabled. Make sure only one is enabled.".to_string()),
});
};

if (use_with_pubkey || use_walletconnect) && (ctx.is_watcher() || ctx.use_watchers()) {
return MmError::err(TendermintInitError {
ticker: ticker.clone(),
kind: TendermintInitErrorKind::CantUseWatchersWithPubkeyPolicy,
});
};

let activation_policy = if let Some(pubkey) = activation_request.with_pubkey {
TendermintActivationPolicy::with_public_key(pubkey)
} else if use_walletconnect {
get_walletconnect_pubkey(
&ctx,
&activation_request.walletconnect,
protocol_conf.chain_id.as_ref(),
&ticker,
)
.await?
} else {
let private_key_policy =
PrivKeyBuildPolicy::detect_priv_key_policy(&ctx).mm_err(|e| TendermintInitError {
Expand Down
2 changes: 0 additions & 2 deletions mm2src/crypto/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,3 @@ tokio = { version = "1.20", default-features = false }
[features]
trezor-udp = ["trezor/trezor-udp"]

[patch.crates-io]
rand_core = "=0.6.4"
98 changes: 98 additions & 0 deletions mm2src/kdf_walletconnect/src/chain/cosmos.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use crate::{error::WalletConnectCtxError, WalletConnectCtx};
use chrono::Utc;
use common::log::info;
use futures::StreamExt;
use mm2_err_handle::prelude::{MmError, MmResult};
use relay_rpc::rpc::params::{session_request::{Request as SessionRequest, SessionRequestRequest},
RequestParams, ResponseParamsSuccess};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use super::WcRequestMethods;

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
pub enum CosmosAccountAlgo {
Secp256k1,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CosmosAccount {
pub address: String,
#[serde(deserialize_with = "deserialize_pubkey")]
pub pubkey: Vec<u8>,
pub algo: CosmosAccountAlgo,
}

fn deserialize_pubkey<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = Value::deserialize(deserializer)?;

match value {
Value::Object(map) => {
let mut vec = Vec::new();
for i in 0..map.len() {
if let Some(Value::Number(num)) = map.get(&i.to_string()) {
if let Some(byte) = num.as_u64() {
vec.push(byte as u8);
} else {
return Err(serde::de::Error::custom("Invalid byte value"));
}
} else {
return Err(serde::de::Error::custom("Invalid pubkey format"));
}
}
Ok(vec)
},
Value::Array(arr) => arr
.into_iter()
.map(|v| {
v.as_u64()
.ok_or_else(|| serde::de::Error::custom("Invalid byte value"))
.map(|n| n as u8)
})
.collect(),
_ => Err(serde::de::Error::custom("Pubkey must be an object or array")),
}
}

pub async fn cosmos_get_accounts_impl(
ctx: &WalletConnectCtx,
chain: &str,
chain_id: &str,
) -> MmResult<Vec<CosmosAccount>, WalletConnectCtxError> {
let account = ctx.get_account_for_chain_id(chain_id).await?;

let session = ctx.session.lock().await;
let session_topic = session.as_ref().map(|s| s.topic.clone());
drop(session);

if let Some(topic) = session_topic {
let request = SessionRequest {
method: WcRequestMethods::CosmosGetAccounts.as_ref().to_owned(),
expiry: Some(Utc::now().timestamp() as u64 + 300),
params: serde_json::to_value(&account).unwrap(),
};
let request = SessionRequestRequest {
request,
chain_id: format!("{chain}:{chain_id}"),
};

let session_request = RequestParams::SessionRequest(request);
ctx.publish_request(&topic, session_request).await?;

let mut session_handler = ctx.session_request_handler.lock().await;
if let Some((message_id, data)) = session_handler.next().await {
info!("Got cosmos account: {data:?}");
let result = serde_json::from_value::<Vec<CosmosAccount>>(data)?;
let response = ResponseParamsSuccess::SessionEvent(true);
ctx.publish_response_ok(&topic, response, &message_id).await?;

return Ok(result);
}
}

MmError::err(WalletConnectCtxError::InvalidRequest)
}
31 changes: 29 additions & 2 deletions mm2src/kdf_walletconnect/src/chain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::collections::{BTreeMap, BTreeSet};
pub mod cosmos;

use relay_rpc::rpc::params::session::{ProposeNamespace, ProposeNamespaces};
use std::collections::{BTreeMap, BTreeSet};

pub(crate) const SUPPORTED_EVENTS: &[&str] = &["chainChanged", "accountsChanged"];

Expand All @@ -10,13 +11,39 @@ pub(crate) const ETH_SUPPORTED_METHODS: &[&str] = &[
"eth_sign",
"personal_sign",
"eth_signTypedData",
"eth_signTypedData_v4",
];
pub(crate) const ETH_SUPPORTED_CHAINS: &[&str] = &["eip155:1", "eip155:5"];

pub(crate) const COSMOS_SUPPORTED_METHODS: &[&str] = &["cosmos_getAccounts", "cosmos_signDirect", "cosmos_signAmino"];
pub(crate) const COSMOS_SUPPORTED_CHAINS: &[&str] = &["cosmos:cosmoshub-4"];

#[derive(Debug, Clone)]
pub enum WcRequestMethods {
EthSendTransaction,
EthSignTransaction,
EthSign,
EthPersonalSign,
EthSignTypedData,
CosmosSignDirect,
CosmosSignAmino,
CosmosGetAccounts,
}

impl AsRef<str> for WcRequestMethods {
fn as_ref(&self) -> &str {
match self {
Self::EthSignTransaction => "eth_signTransaction",
Self::EthSendTransaction => "eth_sendTransaction",
Self::EthSign => "eth_sign",
Self::EthPersonalSign => "personal_sign",
Self::EthSignTypedData => "eth_signTypedData",
Self::CosmosSignDirect => "cosmos_signDirect",
Self::CosmosSignAmino => "cosmos_signAmino",
Self::CosmosGetAccounts => "cosmos_getAccounts",
}
}
}

pub(crate) fn build_required_namespaces() -> ProposeNamespaces {
let mut required = BTreeMap::new();

Expand Down
12 changes: 10 additions & 2 deletions mm2src/kdf_walletconnect/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,22 @@ pub enum WalletConnectCtxError {
PayloadError(String),
#[error("Account not found for chain_id: {0}")]
NoAccountFound(String),
#[error("Account not found for index: {0}")]
NoAccountFoundForIndex(u8),
#[error("Empty account approved for chain_id: {0}")]
EmptyAccount(String),
}

impl From<Error<PublishError>> for WalletConnectCtxError {
fn from(error: Error<PublishError>) -> Self { WalletConnectCtxError::PublishError(format!("{error:?}")) }
fn from(error: Error<PublishError>) -> Self {
WalletConnectCtxError::PublishError(format!("{error:?}"))
}
}

impl From<Error<SubscriptionError>> for WalletConnectCtxError {
fn from(error: Error<SubscriptionError>) -> Self { WalletConnectCtxError::SubscriptionError(format!("{error:?}")) }
fn from(error: Error<SubscriptionError>) -> Self {
WalletConnectCtxError::SubscriptionError(format!("{error:?}"))
}
}

/// Session key and topic derivation errors.
Expand Down
Loading

0 comments on commit 42dd5ba

Please sign in to comment.