Skip to content
2 changes: 1 addition & 1 deletion mm2src/coins/tendermint/htlc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ pub(crate) struct TendermintHtlc {
pub(crate) id: String,

/// Message payload to be sent.
pub(crate) msg_payload: cosmrs::Any,
pub(crate) msg_payload: Any,
}

#[derive(prost::Message)]
Expand Down
165 changes: 139 additions & 26 deletions mm2src/coins/tendermint/tendermint_coin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ const MIN_TIME_LOCK: i64 = 50;

const ACCOUNT_SEQUENCE_ERR: &str = "incorrect account sequence";

pub struct SerializedUnsignedTx {
tx_json: Json,
body_bytes: Vec<u8>,
}

type TendermintPrivKeyPolicy = PrivKeyPolicy<TendermintKeyPair>;

pub struct TendermintKeyPair {
Expand Down Expand Up @@ -346,6 +351,7 @@ pub struct TendermintCoinImpl {
client: TendermintRpcClient,
pub(crate) chain_registry_name: Option<String>,
pub(crate) ctx: MmWeak,
pub(crate) is_keplr_from_ledger: bool,
}

#[derive(Clone)]
Expand Down Expand Up @@ -599,6 +605,7 @@ impl TendermintCommons for TendermintCoin {
}

impl TendermintCoin {
#[allow(clippy::too_many_arguments)]
pub async fn init(
ctx: &MmArc,
ticker: String,
Expand All @@ -607,6 +614,7 @@ impl TendermintCoin {
rpc_urls: Vec<String>,
tx_history: bool,
activation_policy: TendermintActivationPolicy,
is_keplr_from_ledger: bool,
) -> MmResult<Self, TendermintInitError> {
if rpc_urls.is_empty() {
return MmError::err(TendermintInitError {
Expand Down Expand Up @@ -671,6 +679,7 @@ impl TendermintCoin {
client: TendermintRpcClient(AsyncMutex::new(client_impl)),
chain_registry_name: protocol_info.chain_registry_name,
ctx: ctx.weak(),
is_keplr_from_ledger,
})))
}

Expand Down Expand Up @@ -868,19 +877,14 @@ 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 sign_doc = try_tx_s!(self.any_to_sign_doc(account_info, tx_payload, fee, timeout_height, memo));

let unsigned_tx = json!({
"sign_doc": {
"body_bytes": sign_doc.body_bytes,
"auth_info_bytes": sign_doc.auth_info_bytes,
"chain_id": sign_doc.chain_id,
"account_number": sign_doc.account_number,
}
});
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()), unsigned_tx, timeout)
.ask_for_data(&format!("TX_HASH:{}", self.ticker()), tx_json, timeout)
.await
.map_err(|e| ERRL!("{}", e)));

Expand All @@ -892,7 +896,7 @@ impl TendermintCoin {
signatures: tx.signatures,
};

if sign_doc.body_bytes != tx_raw_inner.body_bytes {
if body_bytes != tx_raw_inner.body_bytes {
return Err(crate::TransactionErr::Plain(ERRL!(
"Unsigned transaction don't match with the externally provided transaction."
)));
Expand Down Expand Up @@ -1166,18 +1170,13 @@ impl TendermintCoin {
hex::encode_upper(hash.as_slice()),
))
} else {
let sign_doc = self.any_to_sign_doc(account_info, message, fee, timeout_height, memo)?;

let tx = json!({
"sign_doc": {
"body_bytes": sign_doc.body_bytes,
"auth_info_bytes": sign_doc.auth_info_bytes,
"chain_id": sign_doc.chain_id,
"account_number": sign_doc.account_number,
}
});
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)
}?;

Ok(TransactionData::Unsigned(tx))
Ok(TransactionData::Unsigned(tx_json))
}
}

Expand Down Expand Up @@ -1253,18 +1252,116 @@ impl TendermintCoin {
sign_doc.sign(&signkey)
}

pub(super) fn any_to_sign_doc(
pub(super) fn any_to_serialized_sign_doc(
&self,
account_info: BaseAccount,
tx_payload: Any,
fee: Fee,
timeout_height: u64,
memo: String,
) -> cosmrs::Result<SignDoc> {
) -> cosmrs::Result<SerializedUnsignedTx> {
let tx_body = tx::Body::new(vec![tx_payload], memo, timeout_height as u32);
let pubkey = self.activation_policy.public_key()?.into();
let auth_info = SignerInfo::single_direct(Some(pubkey), account_info.sequence).auth_info(fee);
SignDoc::new(&tx_body, &auth_info, &self.chain_id, account_info.account_number)
let sign_doc = SignDoc::new(&tx_body, &auth_info, &self.chain_id, account_info.account_number)?;

let tx_json = json!({
"sign_doc": {
"body_bytes": sign_doc.body_bytes,
"auth_info_bytes": sign_doc.auth_info_bytes,
"chain_id": sign_doc.chain_id,
"account_number": sign_doc.account_number,
}
});

Ok(SerializedUnsignedTx {
tx_json,
body_bytes: sign_doc.body_bytes,
})
}

/// This should only be used for Keplr from Ledger!
/// When using Keplr from Ledger, they don't accept `SING_MODE_DIRECT` transactions.
///
/// Visit https://docs.cosmos.network/main/build/architecture/adr-050-sign-mode-textual#context for more context.
pub(super) fn any_to_legacy_amino_json(
&self,
account_info: BaseAccount,
tx_payload: Any,
fee: Fee,
timeout_height: u64,
memo: String,
) -> cosmrs::Result<SerializedUnsignedTx> {
const MSG_SEND_TYPE_URL: &str = "/cosmos.bank.v1beta1.MsgSend";
const LEDGER_MSG_SEND_TYPE_URL: &str = "cosmos-sdk/MsgSend";

// Ledger's keplr works as wallet-only, so `MsgSend` support is enough for now.
if tx_payload.type_url != MSG_SEND_TYPE_URL {
return Err(ErrorReport::new(io::Error::new(
io::ErrorKind::Unsupported,
format!(
"Signing mode `SIGN_MODE_LEGACY_AMINO_JSON` is not supported for '{}' transaction type.",
tx_payload.type_url
),
)));
}

let msg_send = MsgSend::from_any(&tx_payload)?;
let timeout_height = u32::try_from(timeout_height)?;
let original_tx_type_url = tx_payload.type_url.clone();
let body_bytes = tx::Body::new(vec![tx_payload], &memo, timeout_height).into_bytes()?;

let amount: Vec<Json> = msg_send
.amount
.into_iter()
.map(|t| {
json!( {
"denom": t.denom,
// Numbers needs to be converted into string type.
// Ref: https://github.com/cosmos/ledger-cosmos/blob/c707129e59f6e0f07ad67161a6b75e8951af063c/docs/TXSPEC.md#json-format
"amount": t.amount.to_string(),
})
})
.collect();

let msg = json!({
"type": LEDGER_MSG_SEND_TYPE_URL,
"value": json!({
"from_address": msg_send.from_address.to_string(),
"to_address": msg_send.to_address.to_string(),
"amount": amount,
})
});

let fee_amount: Vec<Json> = fee
.amount
.into_iter()
.map(|t| {
json!( {
"denom": t.denom,
// Numbers needs to be converted into string type.
// Ref: https://github.com/cosmos/ledger-cosmos/blob/c707129e59f6e0f07ad67161a6b75e8951af063c/docs/TXSPEC.md#json-format
"amount": t.amount.to_string(),
})
})
.collect();

let tx_json = serde_json::json!({
"legacy_amino_json": {
"account_number": account_info.account_number.to_string(),
"chain_id": self.chain_id.to_string(),
"fee": {
"amount": fee_amount,
"gas": fee.gas_limit.to_string()
},
"memo": memo,
"msgs": [msg],
"sequence": account_info.sequence.to_string(),
},
"original_tx_type_url": original_tx_type_url,
});

Ok(SerializedUnsignedTx { tx_json, body_bytes })
}

pub fn add_activated_token_info(&self, ticker: String, decimals: u8, denom: Denom) {
Expand Down Expand Up @@ -2024,6 +2121,13 @@ pub async fn get_ibc_chain_list() -> IBCChainRegistriesResult {
impl MmCoin for TendermintCoin {
fn is_asset_chain(&self) -> bool { false }

fn wallet_only(&self, ctx: &MmArc) -> bool {
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
}

fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.abortable_system) }

fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut {
Expand Down Expand Up @@ -3214,6 +3318,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down Expand Up @@ -3339,6 +3444,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down Expand Up @@ -3401,6 +3507,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down Expand Up @@ -3474,6 +3581,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down Expand Up @@ -3670,6 +3778,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down Expand Up @@ -3752,6 +3861,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down Expand Up @@ -3827,6 +3937,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down Expand Up @@ -3898,6 +4009,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down Expand Up @@ -3952,6 +4064,7 @@ pub mod tendermint_coin_tests {
rpc_urls,
false,
activation_policy,
false,
))
.unwrap();

Expand Down
7 changes: 7 additions & 0 deletions mm2src/coins/tendermint/tendermint_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,13 @@ impl MarketCoinOps for TendermintToken {
impl MmCoin for TendermintToken {
fn is_asset_chain(&self) -> bool { false }

fn wallet_only(&self, ctx: &MmArc) -> bool {
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
}

fn spawner(&self) -> CoinFutSpawner { CoinFutSpawner::new(&self.abortable_system) }

fn withdraw(&self, req: WithdrawRequest) -> WithdrawFut {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub struct TendermintActivationParams {
#[serde(default)]
#[serde(deserialize_with = "deserialize_account_public_key")]
with_pubkey: Option<TendermintPublicKey>,
#[serde(default)]
is_keplr_from_ledger: bool,
}

fn deserialize_account_public_key<'de, D>(deserializer: D) -> Result<Option<TendermintPublicKey>, D::Error>
Expand Down Expand Up @@ -234,6 +236,7 @@ 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();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Can this be a keplr from ledger but not with pubkey?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it can't. If you are using hardware wallet, with-pubkey is the only way you can use your wallet with mm2.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aha, then does it make sense to && activation_request.with_pubkey.is_some(). If we suspect the activation request might be malformed, maybe we could error here instead.


let activation_policy = if let Some(pubkey) = activation_request.with_pubkey {
if ctx.is_watcher() || ctx.use_watchers() {
Expand Down Expand Up @@ -265,6 +268,7 @@ impl PlatformCoinWithTokensActivationOps for TendermintCoin {
activation_request.rpc_urls,
activation_request.tx_history,
activation_policy,
is_keplr_from_ledger,
)
.await
}
Expand Down