-
Notifications
You must be signed in to change notification settings - Fork 98
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
let irn_metadata = param.irn_metadata(); | ||
let ttl = irn_metadata.ttl; |
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 can use a timed/expirable map for this case. we do that for electrum and eth also i think. this is to avoid network-induced memory leaks.
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 mainly went over some of old comments and resolved the ones which i can see have been updated (could use a helping hand in the remaining ones by pointing where the updates are).
my estimation right now is i would need one more all-over iteration for approval (+ old comments), but i don't wanna promise since break my promises :D
Thanks for that huge work!
|
||
self.validate_chain_id(&session, chain_id)?; | ||
|
||
// TODO: uncomment when WalletConnect wallets start listening to chainChanged event |
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.
So nothing happens(I mean wallets don't listen to this event) if we send such request
so what about just sending it for now and when wallets listen to it later we get this feat for free
Two reasons I didn't want to, this will add extra latency and corresponding wallet will return error as they wont't recognize such session request |
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 changes! Here are my last notes.
Will follow up with the changes and approve once we covered/discussed all the important comments.
Value::Object(map) => map | ||
.iter() | ||
.enumerate() | ||
.map(|(_, (_, value))| { |
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 don't use the enumuration indices nor the map iter keys.
so we can replace map.iter().enumerate().map(|(index, (key, value))| {})
by map.value().map(|value| {})
.
.as_u64() | ||
.ok_or_else(|| serde::de::Error::custom("Invalid byte value")) | ||
.and_then(|n| { | ||
if n <= 255 { |
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.
better replace 255
with u8::MAX
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.
n
is still of type u64
so we can't assert n <= u8::MAX
unless we explicitly cast n
to u8
which isn't something we want to do.
let (topic, url) = self.pairing.create(self.metadata.clone(), None)?; | ||
|
||
info!("[{topic}] Subscribing to topic"); |
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.
let's leave a todo here about cleaning up expired pairings that were abandoned.
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 if we leave deleting expired session to the users? enough decentralizations...xd
|
||
pub(crate) fn get_sessions_full(&self) -> impl Iterator<Item = Session> { self.read().clone().into_values() } | ||
|
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: we can return only the pairs of session topics and controllers
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.
idea, I will come back to this...
{ | ||
let mut session = ctx.session_manager.write(); | ||
let Some(session) = session.get_mut(topic) else { | ||
return MmError::err(WalletConnectError::SessionError(format!("No session found for topic: {topic}"))); | ||
}; | ||
session.namespaces = settle.namespaces.0; | ||
session.controller = settle.controller.clone(); | ||
session.relay = settle.relay; | ||
session.expiry = settle.expiry; | ||
|
||
if let Some(value) = settle.session_properties { | ||
let session_properties = serde_json::from_value::<SessionProperties>(value)?; | ||
session.session_properties = Some(session_properties); | ||
}; | ||
}; | ||
|
||
// Update storage session. | ||
let session = ctx | ||
.session_manager | ||
.get_session(topic) | ||
.ok_or(MmError::new(WalletConnectError::SessionError(format!( | ||
"session not found topic: {topic}" | ||
))))?; |
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: we could unscope the codeblock above so not to have to query get_session
one more time.
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.
the scope for session update is on purpose since we can't hold an await
over a sync RwLock, Mutex guard...the most obvious fix is the current arrangement..another fix would be using async mutex
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.
/// Process session settle request.
pub(crate) async fn reply_session_settle_request(
ctx: &WalletConnectCtxImpl,
topic: &Topic,
settle: SessionSettleRequest,
) -> MmResult<(), WalletConnectError> {
let mut sessions = ctx.session_manager.write();
let Some(session) = sessions.get_mut(topic) else {
return MmError::err(WalletConnectError::SessionError(format!("No session found for topic: {topic}")));
};
session.namespaces = settle.namespaces.0;
session.controller = settle.controller.clone();
session.relay = settle.relay;
session.expiry = settle.expiry;
if let Some(value) = settle.session_properties {
let session_properties = serde_json::from_value::<SessionProperties>(value)?;
session.session_properties = Some(session_properties);
};
let session = session.clone();
drop(sessions);
ctx.session_manager
.storage()
.update_session(&session)
.await
.mm_err(|err| WalletConnectError::StorageError(err.to_string()))?;
info!("[{topic}] Session successfully settled for topic");
// Delete other sessions with same controller
// NOTE: we might not want to do this!
let all_sessions = ctx.session_manager.get_sessions_full();
for session in all_sessions {
if session.controller == settle.controller && session.topic.as_ref() != topic.as_ref() {
ctx.drop_session(&session.topic).await?;
debug!("[{}] session deleted", session.topic);
}
}
Ok(())
}
that's what i am suggesting, to drop the mutex before awaiting.
that said, this doesn't compile for some reason that i don't comprehend (still thinks sessions
variable is inscope for some reason).
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.
/// Process session settle request.
pub(crate) async fn reply_session_settle_request(
ctx: &WalletConnectCtxImpl,
topic: &Topic,
settle: SessionSettleRequest,
) -> MmResult<(), WalletConnectError> {
let session = {
let mut sessions = ctx.session_manager.write();
let Some(session) = sessions.get_mut(topic) else {
return MmError::err(WalletConnectError::SessionError(format!("No session found for topic: {topic}")));
};
session.namespaces = settle.namespaces.0;
session.controller = settle.controller.clone();
session.relay = settle.relay;
session.expiry = settle.expiry;
if let Some(value) = settle.session_properties {
let session_properties = serde_json::from_value::<SessionProperties>(value)?;
session.session_properties = Some(session_properties);
};
session.clone()
};
ctx.session_manager
.storage()
.update_session(&session)
.await
.mm_err(|err| WalletConnectError::StorageError(err.to_string()))?;
info!("[{topic}] Session successfully settled for topic");
// Delete other sessions with same controller
// NOTE: we might not want to do this!
let all_sessions = ctx.session_manager.get_sessions_full();
for session in all_sessions {
if session.controller == settle.controller && session.topic.as_ref() != topic.as_ref() {
ctx.drop_session(&session.topic).await?;
debug!("[{}] session deleted", session.topic);
}
}
Ok(())
}
a working sol
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 avoided this because I didn't want to clone.
Actually I think I should have kept the async mutex I used earlier....cloning here is just an unnecessary overhead IMO.
updated bb73b1f
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 avoided this because I didn't want to clone.
calling get_session
does already clone. also serializing this to the DB probably clone as well. that's a tiny overhead anyways.
Actually I think I should have kept the async mutex I used earlier....cloning here is just an unnecessary overhead IMO.
Not at all 😂, having that mutex async just locks the mutex way more than needed, degrading pref. Also being sync guards us in compile time from very nasty mistakes (e.g. forgetting to release the mutex before awaiting on a network request, and boom, you have a disaster of a synchronization mutex).
message_id_generator: MessageIdGenerator, | ||
pending_requests: Mutex<HashMap<MessageId, oneshot::Sender<SessionMessageType>>>, | ||
abortable_system: AbortableQueue, |
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 should really make pending_requests
a timed map.
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?
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.
request handles which we don't get a response for will reside in the map forever
struct SessionManagerImpl { | ||
/// The currently active session topic. | ||
active_topic: Mutex<Option<Topic>>, | ||
/// A thread-safe map of sessions indexed by topic. | ||
sessions: Arc<RwLock<HashMap<Topic, Session>>>, | ||
pub(crate) storage: SessionStorageDb, | ||
} |
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.
with the latest changes, i don't think we need active_topic
any more.
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.
removed active_topic and active_sesson.
/// Get active session topic or return error if no session has been activated. | ||
pub fn get_active_topic_or_err(&self) -> MmResult<Topic, WalletConnectError> { | ||
self.0 | ||
.active_topic | ||
.lock() | ||
.unwrap() | ||
.clone() | ||
.ok_or(MmError::new(WalletConnectError::SessionError( | ||
"No active session".to_owned(), | ||
))) | ||
} | ||
|
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.
and this method is never used.
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.
removed
let wallet_type = if wc.is_ledger_connection() { | ||
TendermintWalletConnectionType::WcLedger(session_topic) | ||
} else { |
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 should check weather this session topic is a ledger connection and not the active session (and should remove the active session field anyway)
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.
done
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()))?; |
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 do we provide callback as the last args? why not get the return value and do the callback here?
doesn't seem intuitive
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 don't want to use WalletConnect lib in coin mod... also taking callback as last args seems practical
https://stackoverflow.com/a/41394746
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 don't want to use WalletConnect lib in coin mod
How is that using walletconnect lib in coins crate? the callback is used at the very end of send_session_request_and_wait
, it could just be taken out.
#1543
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"]