Skip to content

Commit

Permalink
implement walletconnect sign tx for tendermint - wip
Browse files Browse the repository at this point in the history
  • Loading branch information
borngraced committed Oct 2, 2024
1 parent c1accc9 commit 7ab33d5
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 31 deletions.
73 changes: 57 additions & 16 deletions mm2src/coins/tendermint/tendermint_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ use crate::{big_decimal_from_sat_unsigned, BalanceError, BalanceFut, BigDecimal,
WatcherValidateTakerFeeInput, WithdrawError, WithdrawFee, WithdrawFut, WithdrawRequest};
use async_std::prelude::FutureExt as AsyncStdFutureExt;
use async_trait::async_trait;
use base64::engine::general_purpose;
use base64::Engine;
use bip32::DerivationPath;
use bitcrypto::{dhash160, sha256};
use common::executor::{abortable_queue::AbortableQueue, AbortableSystem};
Expand Down Expand Up @@ -61,6 +63,7 @@ use futures01::Future;
use hex::FromHexError;
use instant::Duration;
use itertools::Itertools;
use kdf_walletconnect::WalletConnectCtx;
use keys::{KeyPair, Public};
use mm2_core::mm_ctx::{MmArc, MmWeak};
use mm2_err_handle::prelude::*;
Expand Down Expand Up @@ -354,6 +357,13 @@ impl RpcCommonOps for TendermintCoin {
}
}

#[derive(PartialEq)]
pub enum TendermintWalletConnectionType {
KeplrLedger,
WalletConnect,
Other,
}

pub struct TendermintCoinImpl {
ticker: String,
/// As seconds
Expand All @@ -374,7 +384,7 @@ pub struct TendermintCoinImpl {
client: TendermintRpcClient,
pub(crate) chain_registry_name: Option<String>,
pub(crate) ctx: MmWeak,
pub(crate) is_keplr_from_ledger: bool,
pub(crate) wallet_connection_type: TendermintWalletConnectionType,
}

#[derive(Clone)]
Expand Down Expand Up @@ -642,7 +652,7 @@ impl TendermintCoin {
nodes: Vec<RpcNode>,
tx_history: bool,
activation_policy: TendermintActivationPolicy,
is_keplr_from_ledger: bool,
wallet_connection_type: TendermintWalletConnectionType,
) -> MmResult<Self, TendermintInitError> {
if nodes.is_empty() {
return MmError::err(TendermintInitError {
Expand Down Expand Up @@ -707,7 +717,7 @@ impl TendermintCoin {
client: TendermintRpcClient(AsyncMutex::new(client_impl)),
chain_registry_name: protocol_info.chain_registry_name,
ctx: ctx.weak(),
is_keplr_from_ledger,
wallet_connection_type,
})))
}

Expand Down Expand Up @@ -907,32 +917,59 @@ impl TendermintCoin {
let ctx = try_tx_s!(MmArc::from_weak(&self.ctx).ok_or(ERRL!("ctx must be initialized already")));

let account_info = try_tx_s!(self.account_info(&self.account_id).await);
let SerializedUnsignedTx { tx_json, body_bytes } = if self.is_keplr_from_ledger {
let SerializedUnsignedTx { tx_json, body_bytes } = if self.is_keplr_from_ledger() {
try_tx_s!(self.any_to_legacy_amino_json(&account_info, tx_payload, fee, timeout_height, memo))
} else {
try_tx_s!(self.any_to_serialized_sign_doc(&account_info, tx_payload, fee, timeout_height, memo))
};

let data: TxHashData = try_tx_s!(ctx
.ask_for_data(&format!("TX_HASH:{}", self.ticker()), tx_json, timeout)
.ask_for_data(&format!("TX_HASH:{}", self.ticker()), tx_json.clone(), timeout)
.await
.map_err(|e| ERRL!("{}", e)));

let tx = try_tx_s!(self.request_tx(data.hash.clone()).await.map_err(|e| ERRL!("{}", e)));

let tx_raw_inner = TxRaw {
body_bytes: tx.body.as_ref().map(Message::encode_to_vec).unwrap_or_default(),
auth_info_bytes: tx.auth_info.as_ref().map(Message::encode_to_vec).unwrap_or_default(),
signatures: tx.signatures,
let tx_raw = match self.wallet_connection_type {
TendermintWalletConnectionType::WalletConnect => {
let wallet_connect =
try_tx_s!(WalletConnectCtx::try_from_ctx_or_initialize(&ctx).map_err(|e| ERRL!("{}", e)));
let my_address = try_tx_s!(self.my_address().map_err(|e| ERRL!("{}", e)));
let response = try_tx_s!(
wallet_connect
.cosmos_send_sign_tx_request(tx_json, &self.chain_id.to_string(), my_address)
.await
);
let signature = try_tx_s!(general_purpose::STANDARD
.decode(response.signature.signature)
.map_err(|e| ERRL!("{}", e)));
let body_bytes = try_tx_s!(general_purpose::STANDARD
.decode(response.signed.body_bytes)
.map_err(|e| ERRL!("{}", e)));
let auth_info_bytes = try_tx_s!(general_purpose::STANDARD
.decode(response.signed.auth_info_bytes)
.map_err(|e| ERRL!("{}", e)));
TxRaw {
body_bytes,
auth_info_bytes,
signatures: vec![signature],
}
},
_ => {
let tx = try_tx_s!(self.request_tx(data.hash.clone()).await.map_err(|e| ERRL!("{}", e)));
TxRaw {
body_bytes: tx.body.as_ref().map(Message::encode_to_vec).unwrap_or_default(),
auth_info_bytes: tx.auth_info.as_ref().map(Message::encode_to_vec).unwrap_or_default(),
signatures: tx.signatures,
}
},
};

if body_bytes != tx_raw_inner.body_bytes {
if body_bytes != tx_raw.body_bytes {
return Err(crate::TransactionErr::Plain(ERRL!(
"Unsigned transaction don't match with the externally provided transaction."
)));
}

Ok((data.hash, Raw::from(tx_raw_inner)))
Ok((data.hash, Raw::from(tx_raw)))
}

#[allow(deprecated)]
Expand Down Expand Up @@ -1204,7 +1241,7 @@ impl TendermintCoin {
hex::encode_upper(hash.as_slice()),
))
} else {
let SerializedUnsignedTx { tx_json, .. } = if self.is_keplr_from_ledger {
let SerializedUnsignedTx { tx_json, .. } = if self.is_keplr_from_ledger() {
self.any_to_legacy_amino_json(account_info, message, fee, timeout_height, memo)
} else {
self.any_to_serialized_sign_doc(account_info, message, fee, timeout_height, memo)
Expand Down Expand Up @@ -2085,6 +2122,10 @@ impl TendermintCoin {

None
}

pub fn is_keplr_from_ledger(&self) -> bool {
TendermintWalletConnectionType::KeplrLedger == self.wallet_connection_type
}
}

fn clients_from_urls(ctx: &MmArc, nodes: Vec<RpcNode>) -> MmResult<Vec<HttpClient>, TendermintInitErrorKind> {
Expand Down Expand Up @@ -2169,7 +2210,7 @@ impl MmCoin for TendermintCoin {
let coin_conf = crate::coin_conf(ctx, self.ticker());
let wallet_only_conf = coin_conf["wallet_only"].as_bool().unwrap_or(false);

wallet_only_conf || self.is_keplr_from_ledger
wallet_only_conf || self.is_keplr_from_ledger()
}

fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.abortable_system) }
Expand Down Expand Up @@ -2253,7 +2294,7 @@ impl MmCoin for TendermintCoin {
)
.await?;

let fee_amount_u64 = if coin.is_keplr_from_ledger {
let fee_amount_u64 = if coin.is_keplr_from_ledger() {
// When using `SIGN_MODE_LEGACY_AMINO_JSON`, Keplr ignores the fee we calculated
// and calculates another one which is usually double what we calculate.
// To make sure the transaction doesn't fail on the Keplr side (because if Keplr
Expand Down
2 changes: 1 addition & 1 deletion mm2src/coins/tendermint/tendermint_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ impl MmCoin for TendermintToken {
let coin_conf = crate::coin_conf(ctx, self.ticker());
let wallet_only_conf = coin_conf["wallet_only"].as_bool().unwrap_or(false);

wallet_only_conf || self.platform_coin.is_keplr_from_ledger
wallet_only_conf || self.platform_coin.is_keplr_from_ledger()
}

fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.abortable_system) }
Expand Down
15 changes: 9 additions & 6 deletions mm2src/coins_activation/src/tendermint_with_assets_activation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use coins::tendermint::tendermint_tx_history_v2::tendermint_history_loop;
use coins::tendermint::{tendermint_priv_key_policy, RpcNode, TendermintActivationPolicy, TendermintCoin,
TendermintCommons, TendermintConf, TendermintInitError, TendermintInitErrorKind,
TendermintProtocolInfo, TendermintPublicKey, TendermintToken, TendermintTokenActivationParams,
TendermintTokenInitError, TendermintTokenProtocolInfo};
TendermintTokenInitError, TendermintTokenProtocolInfo, TendermintWalletConnectionType};
use coins::{CoinBalance, CoinProtocol, MarketCoinOps, MmCoin, MmCoinEnum, PrivKeyBuildPolicy};
use common::executor::{AbortSettings, SpawnAbortable};
use common::{true_f, Future01CompatExt};
Expand Down Expand Up @@ -293,10 +293,9 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin {
protocol_conf: Self::PlatformProtocolInfo,
) -> Result<Self, MmError<Self::ActivationError>> {
let conf = TendermintConf::try_from_json(&ticker, coin_conf)?;
let mut is_keplr_from_ledger = false;
let mut wallet_connectin_type = TendermintWalletConnectionType::Other;

let activation_policy = if let Some(params) = activation_request.activation_params {
println!("{params:?}");
if ctx.is_watcher() || ctx.use_watchers() {
return MmError::err(TendermintInitError {
ticker: ticker.clone(),
Expand All @@ -307,12 +306,16 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin {
match params {
TendermintPubkeyActivationParams::WithPubkey {
pubkey,
is_keplr_from_ledger: temp,
is_keplr_from_ledger,
} => {
is_keplr_from_ledger = temp;
if is_keplr_from_ledger {
wallet_connectin_type = TendermintWalletConnectionType::KeplrLedger;
};

TendermintActivationPolicy::with_public_key(pubkey)
},
TendermintPubkeyActivationParams::WalletConnect(params) => {
wallet_connectin_type = TendermintWalletConnectionType::WalletConnect;
get_walletconnect_pubkey(&ctx, &params, protocol_conf.chain_id.as_ref(), &ticker).await?
},
}
Expand All @@ -337,7 +340,7 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin {
activation_request.nodes,
activation_request.tx_history,
activation_policy,
is_keplr_from_ledger,
wallet_connectin_type,
)
.await
}
Expand Down
105 changes: 102 additions & 3 deletions mm2src/kdf_walletconnect/src/chain/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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 serde_json::{json, Value};

use super::WcRequestMethods;

Expand Down Expand Up @@ -69,7 +69,6 @@ where

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?;
Expand All @@ -87,7 +86,7 @@ pub async fn cosmos_get_accounts_impl(
};
let request = SessionRequestRequest {
request,
chain_id: format!("{chain}:{chain_id}"),
chain_id: format!("cosmos:{chain_id}"),
};

let session_request = RequestParams::SessionRequest(request);
Expand All @@ -106,3 +105,103 @@ pub async fn cosmos_get_accounts_impl(

MmError::err(WalletConnectCtxError::InvalidRequest)
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CosmosTxSignedData {
pub signature: CosmosTxSignature,
pub signed: CosmosSignData,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CosmosTxSignature {
pub pub_key: CosmosTxPublicKey,
pub signature: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CosmosTxPublicKey {
#[serde(rename = "type")]
pub key_type: String,
pub value: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CosmosSignData {
pub chain_id: String,
pub account_number: String,
pub auth_info_bytes: String,
pub body_bytes: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CosmosTxSignParams {
pub signer_address: String,
pub sign_doc: CosmosSignData,
}

pub async fn cosmos_sign_tx_direct_impl(
ctx: &WalletConnectCtx,
sign_doc: Value,
chain_id: &str,
signer_address: String,
) -> MmResult<CosmosTxSignedData, WalletConnectCtxError> {
let session_topic = {
let session = ctx.session.lock().await;
session.as_ref().map(|s| s.topic.clone())
};

// return not NotInitialized error if no session is found.
if session_topic.is_none() {
return MmError::err(WalletConnectCtxError::NotInitialized);
}

let value = json!({
"signer_address": signer_address,
"sign_doc": sign_doc
});

let request = SessionRequest {
method: WcRequestMethods::CosmosSignDirect.as_ref().to_owned(),
expiry: Some(Utc::now().timestamp() as u64 + 300),
params: value,
};
let request = SessionRequestRequest {
request,
chain_id: format!("cosmos:{chain_id}"),
};

let session_request = RequestParams::SessionRequest(request);
let topic = session_topic.unwrap();
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 sign response: {data:?}");
let result = serde_json::from_value::<CosmosTxSignedData>(data)?;
let response = ResponseParamsSuccess::SessionEvent(true);
ctx.publish_response_ok(&topic, response, &message_id).await?;

return Ok(result);
}

MmError::err(WalletConnectCtxError::InternalError(
"No response from wallet".to_string(),
))
}

//{
// "id": 1,
// "jsonrpc": "2.0",
// "method": "cosmos_signDirect",
// "params": {
// "signerAddress": "cosmos1sguafvgmel6f880ryvq8efh9522p8zvmrzlcrq",
// "signDoc": {
// "chainId": "cosmoshub-4",
// "accountNumber": "1"
// "authInfoBytes": "CgoKABIECgIIARgBEhMKDQoFdWNvc20SBDIwMDAQwJoM",
// "bodyBytes": "CpABChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEnAKLWNvc21vczFwa3B0cmU3ZmRrbDZnZnJ6bGVzamp2aHhobGMzcjRnbW1rOHJzNhItY29zbW9zMXF5cHF4cHE5cWNyc3N6ZzJwdnhxNnJzMHpxZzN5eWM1bHp2N3h1GhAKBXVjb3NtEgcxMjM0NTY3"
// }
// }
// }
Loading

0 comments on commit 7ab33d5

Please sign in to comment.