Skip to content
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

Open
wants to merge 172 commits into
base: dev
Choose a base branch
from
Open

Conversation

borngraced
Copy link
Member

@borngraced borngraced commented Sep 16, 2024

#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:

  • Implement multi-session handling for concurrent WalletConnect connections across accounts, integrates SQLite/IndexedDB for persistent session storage across devices, enhances relayer disconnection handling for smoother session management
  • Implemented WalletConnect coin activation for Tendermint and EVM.
  • Implemented Withdraw and Swap functionalities for Tendermint and EVM

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)

  1. Start kdf (cargo run)
  2. Create WalletConnect session connection
    • Use the RPC below
    • Copy the connection URL and either:
      • Paste directly in your wallet or
      • Generate QR code using QR Code Generator and scan with your wallet (e.g., MetaMask)
{
     "method": "wc_new_connection",
	"userpass": "{{ _.userpass }}",
	"mmrpc": "2.0",
	"params": {
		"required_namespaces": {
			"eip155": {
				"chains": ["eip155:1"],
				"methods": [
					"eth_sendTransaction",
					"eth_signTransaction",
					"personal_sign"
				],
				"events": [
					"accountsChanged",
					"chainChanged"
				]
			}
		}
	}
}
  1. Activate ETH coin (set "priv_key_policy": "WalletConnect", in activation params).
  2. Approve authentication request in your Wallet
  3. Withdraw or trade.

Note: To add more eip155 chains, modify the chains array like this: ["eip155:1", "eip155:250"]

@borngraced borngraced self-assigned this Sep 16, 2024
mm2src/kdf_walletconnect/src/lib.rs Outdated Show resolved Hide resolved
mm2src/kdf_walletconnect/src/lib.rs Outdated Show resolved Hide resolved
mm2src/kdf_walletconnect/src/lib.rs Outdated Show resolved Hide resolved
let irn_metadata = param.irn_metadata();
let ttl = irn_metadata.ttl;
Copy link
Collaborator

@mariocynicys mariocynicys Dec 12, 2024

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

mm2src/kdf_walletconnect/src/inbound_message.rs Outdated Show resolved Hide resolved
mm2src/kdf_walletconnect/src/session/rpc/delete.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@mariocynicys mariocynicys left a 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!

mm2src/kdf_walletconnect/src/lib.rs Outdated Show resolved Hide resolved
mm2src/kdf_walletconnect/src/lib.rs Outdated Show resolved Hide resolved
mm2src/kdf_walletconnect/src/session/rpc/settle.rs Outdated Show resolved Hide resolved

self.validate_chain_id(&session, chain_id)?;

// TODO: uncomment when WalletConnect wallets start listening to chainChanged event
Copy link
Collaborator

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

@borngraced
Copy link
Member Author

borngraced commented Dec 26, 2024

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

Copy link
Collaborator

@mariocynicys mariocynicys left a 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.

mm2src/coins/tendermint/wallet_connect.rs Outdated Show resolved Hide resolved
mm2src/coins/tendermint/wallet_connect.rs Show resolved Hide resolved
Comment on lines +235 to +237
let (topic, url) = self.pairing.create(self.metadata.clone(), None)?;

info!("[{topic}] Subscribing to topic");
Copy link
Collaborator

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.

Copy link
Member Author

@borngraced borngraced Jan 9, 2025

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? more decentralizations...xd

Comment on lines 299 to 301

pub(crate) fn get_sessions_full(&self) -> impl Iterator<Item = Session> { self.read().clone().into_values() }

Copy link
Collaborator

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

Copy link
Member Author

@borngraced borngraced Jan 9, 2025

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...

update: c697e3c

mm2src/kdf_walletconnect/src/session/rpc/settle.rs Outdated Show resolved Hide resolved
Comment on lines 84 to 86
message_id_generator: MessageIdGenerator,
pending_requests: Mutex<HashMap<MessageId, oneshot::Sender<SessionMessageType>>>,
abortable_system: AbortableQueue,
Copy link
Collaborator

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

why?

Copy link
Collaborator

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

Copy link
Member Author

Choose a reason for hiding this comment

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

This will required updating structures in WalletConnect rust lib with some trait bounds.. I guess this can be postponed

Copy link
Collaborator

Choose a reason for hiding this comment

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

will it really?
ok let's leave a todo for it then.

mm2src/kdf_walletconnect/src/session/mod.rs Outdated Show resolved Hide resolved
mm2src/kdf_walletconnect/src/session/mod.rs Outdated Show resolved Hide resolved
mm2src/coins/tendermint/wallet_connect.rs Outdated Show resolved Hide resolved
Copy link
Collaborator

@mariocynicys mariocynicys left a comment

Choose a reason for hiding this comment

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

Nearly done :)

},
)
.await
Ok(accounts[0].clone())
Copy link
Collaborator

Choose a reason for hiding this comment

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

might panic
let use .first().ok_or_err()

mm2_err_handle = { path = "../mm2_err_handle" }
mm2_test_helpers = { path = "../mm2_test_helpers" }
Copy link
Collaborator

Choose a reason for hiding this comment

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

what's this for?

Copy link
Member Author

Choose a reason for hiding this comment

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

not needed anymore, removed

Comment on lines 186 to 187
let data = wc
.send_session_request_and_wait(
session_topic,
&chain_id,
WcRequestMethods::PersonalSign,
params,
|data: String| {
extract_pubkey_from_signature(&data, message, &account_str)
.mm_err(|err| WalletConnectError::SessionError(err.to_string()))
},
)
.send_session_request_and_wait::<String>(session_topic, &chain_id, WcRequestMethods::PersonalSign, params)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: let data: String = ... > let data = send_session_request::<String>(...

Copy link
Member Author

@borngraced borngraced Jan 20, 2025

Choose a reason for hiding this comment

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

this only makes a difference if send_session_request has some complex type annotations

Comment on lines 72 to 77
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() {
if session_controller_exists && session.topic.as_ref() != topic.as_ref() {
ctx.drop_session(&session.topic).await?;
debug!("[{}] session deleted", session.topic);
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

that doens't make sense. if session_controller_exists is true, that will delete all the sessions sessions with other topics.
we should be checking each session's controller.

Copy link
Member Author

Choose a reason for hiding this comment

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

thanks for the catch!

Comment on lines 203 to 202
if n <= 255 {
if n <= 0xff {
Copy link
Collaborator

Choose a reason for hiding this comment

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

that's more confusing tbh, i would rather revert back to 255 😂

or u8::MAX with casting (.into, as u64, etc..)

Copy link
Member Author

@borngraced borngraced Jan 20, 2025

Choose a reason for hiding this comment

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

it's for aestethics 😆 ..reverted to 255

Comment on lines +97 to +98
.session_topic()
.expect("TODO: handle after updating tendermint coin init");
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's easy to return an error here? no?
let's not panic.

Comment on lines 196 to 197
.iter()
.enumerate()
.map(|(_, (_, value))| {
.map(|(_, value)| {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: map.value().map(|value| {}) > map.iter().map(|(_unused_key, value)| {})

Copy link
Member Author

Choose a reason for hiding this comment

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

value.map.iter().map(|(_unused_key, value)| {}) > value.1.map.value().map(|value| {}) :p

Comment on lines 84 to 86
message_id_generator: MessageIdGenerator,
pending_requests: Mutex<HashMap<MessageId, oneshot::Sender<SessionMessageType>>>,
abortable_system: AbortableQueue,
Copy link
Collaborator

Choose a reason for hiding this comment

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

will it really?
ok let's leave a todo for it then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants