-
Notifications
You must be signed in to change notification settings - Fork 94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(walletconnect): walletconnect integration #2223
base: dev
Are you sure you want to change the base?
Conversation
@onur-ozkan This PR requires your review on the tendermint code changes at least |
@borngraced cargo lock conflict occurred |
mm2src/coins_activation/src/tendermint_with_assets_activation.rs
Outdated
Show resolved
Hide resolved
/// Activation via WalletConnect | ||
WalletConnect, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some info about why we don't take public key similar to how it's done with WithPubkey
variant?
Also, I'm curious why WithPubkey
mode doesn’t work here. Doesn’t WalletConnect already provide the public key via an API? WithPubkey
mode was intended to support any type of external wallet as it only requires the public key and doesn’t depend on the wallet’s internal logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WalletConnect
doesn’t directly provide the public key as part of the initial session information. Instead, it only returns the account address, methods and the chains the user can interact with. To obtain the public key, a separate request is required for the specific account
https://specs.walletconnect.com/2.0/specs/clients/sign/rpc-methods#wc_sessionsettle
https://specs.walletconnect.com/2.0/specs/clients/sign/session-proposal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have done it in a way where some library/crate (or even GUI side) handles this and then utilizes with_pubkey
on core KDF side to have single entrypoint for any external wallet, so the complexity remains constant for N external wallet types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is like dividing this implementation into two (manages connection from GUI while kdf manages request handling ) or leaving everything entirely to GUI
which is not what we wanted from the beginning.
So the thing is whoever wishes to use any wallet pubkey via WalletConnect needs to make an additional request which can't really be avoided.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I mean was, we could handle that connection part and the extra request part (for getting pubkey) in a separate crate and utilize the with_pubkey
mode. I mean, we have an abstraction layer for external wallets, so why not using it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we could handle that connection part and the extra request part (for getting pubkey) in a separate crate
how do you plan to do this? since, we either ways we still need to request for pubkey
meanwhile with_pubkey
params requires pubkey
in activation params itself. There's a kdf_walletconnect crate that handles all WC logic By the way,.
WalletConnect
coin activation mode lets the activation logic handle the session and fetch the key directly, setting it in TendermintActivationPolicy::PublicKey
—keeping things simple without needing an abstraction layer.
Also, the current external wallet implementation for tendermint
uses SSE
, in the case of Keplr
, we need to send request to GUI
then to Keplr wallet
if I'm correct, so it can't directly work with WalletConnect
. The current activation logic is quite easy to understand and scale(when integrating KDF as Wallet in the future)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how do you plan to do this? since, we either ways we still need to request for pubkey meanwhile with_pubkey params requires pubkey in activation params itself. There's a kdf_walletconnect crate that handles all WC logic By the way,.
User sends the activation request for WC -> KDF takes the request and runs the logic where it reads Pubkey from WC -> Uses that Pubkey to run KDF as an external wallet (just like any other wallet).
Is that not possible?
Also, the current external wallet implementation for tendermint uses SSE, in the case of Keplr, we need to send request to GUI then to Keplr wallet if I'm correct, so it can't directly work with WalletConnect. The current activation logic is quite easy to understand and scale(when integrating KDF as Wallet in the future)
Yeah, for Keplr, GUI side triggers the Keplr events which when required. But it doesn't have to be the same way for WalletConnect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
User sends the activation request for WC -> KDF takes the request and runs the logic where it reads Pubkey from WC -> Uses that Pubkey to run KDF as an external wallet (just like any other wallet).
that's how the current flow works.. or am getting something wrong..
User request coin activation, logic checks if TendermintPubkeyActivationParams::WalletConnect
( we can't use WithPubkey, since we don't have the pubkey at start)
komodo-defi-framework/mm2src/coins_activation/src/tendermint_with_assets_activation.rs
Lines 306 to 308 in f5caa19
TendermintPubkeyActivationParams::WalletConnect => { | |
activate_with_walletconnect(&ctx, protocol_conf.chain_id.as_ref(), &ticker).await? | |
}, |
then KDF takes the request and runs the logic where it reads Pubkey from WC( maybe this function should have a different naming)
komodo-defi-framework/mm2src/coins_activation/src/tendermint_with_assets_activation.rs
Lines 232 to 260 in f5caa19
async fn activate_with_walletconnect( | |
ctx: &MmArc, | |
chain_id: &str, | |
ticker: &str, | |
) -> MmResult<(TendermintActivationPolicy, TendermintWalletConnectionType), TendermintInitError> { | |
let wc = WalletConnectCtx::from_ctx(ctx).expect("WalletConnectCtx should be initialized by now!"); | |
let account = cosmos_get_accounts_impl(&wc, chain_id) | |
.await | |
.mm_err(|err| TendermintInitError { | |
ticker: ticker.to_string(), | |
kind: TendermintInitErrorKind::UnableToFetchChainAccount(err.to_string()), | |
})?; | |
let wallet_type = if wc.is_ledger_connection().await { | |
TendermintWalletConnectionType::WcLedger | |
} else { | |
TendermintWalletConnectionType::Wc | |
}; | |
let pubkey = match account.algo { | |
CosmosAccountAlgo::Secp256k1 | CosmosAccountAlgo::TendermintSecp256k1 => { | |
TendermintPublicKey::from_raw_secp256k1(&account.pubkey).ok_or(TendermintInitError { | |
ticker: ticker.to_string(), | |
kind: TendermintInitErrorKind::Internal("Invalid secp256k1 pubkey".to_owned()), | |
})? | |
}, | |
}; | |
Ok((TendermintActivationPolicy::with_public_key(pubkey), wallet_type)) | |
} |
Uses that Pubkey to run KDF as an external wallet (just like any other wallet).
komodo-defi-framework/mm2src/coins_activation/src/tendermint_with_assets_activation.rs
Line 259 in f5caa19
Ok((TendermintActivationPolicy::with_public_key(pubkey), wallet_type)) |
@@ -276,6 +279,7 @@ impl TendermintActivationPolicy { | |||
|
|||
#[cfg(target_arch = "wasm32")] | |||
PrivKeyPolicy::Metamask(_) => unreachable!(), | |||
PrivKeyPolicy::WalletConnect { .. } => unreachable!(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: Is it really impossible to get the public key from WalletConnect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean privateKey?, No
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read it wrong
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method gets the pubkey though.
if i understand correctly this is marked as unreachable as we convert walletconnect variants to pubkey-only.
can we return an error here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! Very organized and easy to review.
Here is the first iteration.
@@ -2,7 +2,7 @@ | |||
JEMALLOC_SYS_WITH_MALLOC_CONF = "background_thread:true,narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0,metadata_thp:auto" | |||
|
|||
[target.'cfg(all())'] | |||
rustflags = [ "-Zshare-generics=y" ] | |||
rustflags = [ "-Zshare-generics=y", '--cfg=curve25519_dalek_backend="fiat"' ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: what is that for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
selecting the backend for compiling curve25519-dalek. This is explicitly needed because I updated the lib which now supports different backends for compilation
https://crates.io/crates/curve25519-dalek/4.1.3#simd-backend
[patch.crates-io] | ||
rand_core = { version = "0.6.2", package = "bip39" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: can't understand this patch. why doesn't it have a url/git? could you explain how/what this does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I'm forcing bip39 to depend on rand_core 0.6.2 since the lib is selecting the least supported version(0.4.2) in kdf even tho the version isn't really compatible with the latest version of bip39
https://crates.io/crates/bip39/2.1.0/dependencies, I mean the feature of bip39 we using in kdf requires rand_core 0.6.2 or greater
The lib is configured to select any version from 0.4.2==0.7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another short iteration. i think i will focus solely on the new crate in the next iteration since the rest looks good.
the new crate i think is very well organized and easy to traverse. could make use of excessive spamy detailed (doc) comments though. this way others who didn't review it could still understand and change it.
@@ -276,6 +279,7 @@ impl TendermintActivationPolicy { | |||
|
|||
#[cfg(target_arch = "wasm32")] | |||
PrivKeyPolicy::Metamask(_) => unreachable!(), | |||
PrivKeyPolicy::WalletConnect { .. } => unreachable!(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method gets the pubkey though.
if i understand correctly this is marked as unreachable as we convert walletconnect variants to pubkey-only.
can we return an error here instead?
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 format")); | ||
} | ||
} | ||
Ok(vec) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit suggestion:
map.values()
.map(|v| {
v.as_u64()
.map(|v| v as u8)
.ok_or_else(|| serde::de::Error::custom("Invalid byte value"))
})
.collect()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these aren't equivalent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only diff i think is Some(Value::Number(num)) = map.get()
to check if the value is a number? right?
I don't think that's really important to set one error message for numbers vs non-numbers bad bytes. also we should do the same for conversion from u64
to u8
.
mm2src/mm2_main/src/rpc.rs
Outdated
@@ -205,7 +205,7 @@ async fn process_single_request(ctx: MmArc, req: Json, client: SocketAddr) -> Re | |||
|
|||
#[cfg(not(target_arch = "wasm32"))] | |||
async fn rpc_service(req: Request<Body>, ctx_h: u32, client: SocketAddr) -> Response<Body> { | |||
const NON_ALLOWED_CHARS: &[char] = &['<', '>', '&']; | |||
const NON_ALLOWED_CHARS: &[char] = &['Ò']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's that char and why is it disallowed? if it's a place holder we could instead remove this validation all together.
}; | ||
|
||
let mut public = Public::default(); | ||
public.as_mut().copy_from_slice(&uncompressed[1..65]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ummm, looks like we skip the first 8bits of the H520
, thus rendering it essentially an H512
. maybe we could return a Public = H512
instead of PublicKey = H520
from recover()
.
that's just an early suggestion, i will want to check what these trimmed 8bits actually for first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this changed needed ? I think its more appropriate to return Seckp2561 pubkey here
komodo-defi-framework/mm2src/coins/eth/wallet_connect.rs
Lines 211 to 222 in 0d3fdfe
pub(crate) fn recover(signature: &Signature, message: &Message) -> Result<PublicKey, ethkey::Error> { | |
let recovery_id = { | |
let recovery_id = (signature[64] as i32) | |
.checked_sub(27) | |
.ok_or_else(|| ethkey::Error::InvalidSignature)?; | |
RecoveryId::from_i32(recovery_id)? | |
}; | |
let sig = RecoverableSignature::from_compact(&signature[0..64], recovery_id)?; | |
let pubkey = Secp256k1::new().recover(&secp256k1::Message::from_slice(&message[..])?, &sig)?; | |
Ok(pubkey) | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not really needed. but I'm trying to understand why we have two different pubkey types and making them into one if possible.
will dig more into it.
This PR introduces the integration of WalletConnect into the Komodo DeFi Framework (KDF), enabling secure wallet connections for Cosmos and EVM-based chains. KDF acts as the DApp(in this PR), allowing users to initiate and manage transactions securely with external wallets e.g via Metamask.
Key changes include:
Tendermint
andEVM
.Tendermint
andEVM
https://specs.walletconnect.com/2.0/specs/clients/sign/
https://specs.walletconnect.com/2.0/specs/clients/core/pairing/
https://specs.walletconnect.com/2.0/specs/clients/core/crypto/
https://specs.walletconnect.com/2.0/specs/servers/relay/
https://docs.reown.com/advanced/multichain/rpc-reference/ethereum-rpc
Additional improvements include cleanup of unused dependencies, minor code refinements, and WASM compatibility
Updated deps:
Added deps:
Removed deps:
How to test using EVM coin e.g
ETH
(Native && WASM)cargo run
)ETH
coin (set"priv_key_policy": "WalletConnect",
in activation params).Note: To add more
eip155
chains, modify the chains array like this:["eip155:1", "eip155:250"]