Crates.io | bindings-abstraction-wc-modal |
lib.rs | bindings-abstraction-wc-modal |
version | 0.1.0 |
source | src |
created_at | 2024-05-24 12:33:33.076215 |
updated_at | 2024-05-24 12:33:33.076215 |
description | A WASM Abstraction for WalletConnect Modal Bindings |
homepage | https://github.com/63b8e7lmk1jv9c0ovus4hqkcasqgp0ed/wasm-bindings-abstraction-for-walletconnect-modal |
repository | https://github.com/63b8e7lmk1jv9c0ovus4hqkcasqgp0ed/wasm-bindings-abstraction-for-walletconnect-modal |
max_upload_size | |
id | 1251058 |
size | 2,448,568 |
This is an abstraction for bindings created for WalletConnect's Modal. The cargo doc --target wasm32-unknown-unknown --open
provides information on using this library.
It seemed complicated to use WalletConnect with a Rust WASM frontend and I wanted the bindings for my own projects.
The goal of this abstraction is just to make it easy to use WalletConnect's Modal with Rust based Ethereum/blockchain providers. (ethers-rs, alloy-rs, web3-rs, etc) Though, currently this crate only supports ethers-rs. This was also written a while ago, so the packages from NPM are not new. (I also have not searched for other libraries that might serve the same purpose as this one for a while)
I used writing this library to experiment with Rust in general.
One of the goals in writing it was to simplify the bridge from the dynamic typing in JS to something solid and reliable in Rust. I think it's overcomplicated internally, my original attempts to wrap JsValue's are hard to follow.
A struct with internal JsValues values cannot directly interact with the trait requirements for ethers, alloy, and other providers because they require Send + Sync restrictions. Without an existing runtime, a safe Rust solution is to use message sending with a library like async_channel and a runtime/server to listen for these messages. This is what this crate does, it acts a runtime/server, and providers requests send messages to pass the trait restrictions from the Rust based providers.
The current version to perform a JSON request against the JS binded RPC provider looks something like the following
let request = RequestFromTransport {
full_rpc_request: json_request,
queue_type: queue_type,
send_back_to: send,
started: false,
start_time: None,
queue_time: Utc::now(),
};
if let Err(_e) = server_cmd_cli
.send(ClientCommands::RpcRequest(request))
.await
{
return Err(WalTransportFailure {
msg: "Channel closed. This should never happen. Open an issue: {_e:?}".into(),
failure: WalletConnectTransportFailure::Json(JsonFailure::JsDe),
});
}
if let Ok(result) = receieve.recv().await {
result
} else {
return Err(WalTransportFailure {
msg: format!("Channel closed. This should never happen. Open an issue"),
failure: WalletConnectTransportFailure::Json(JsonFailure::JsDe),
});
}
Which has the associated runtime/server that is spawned to run as a promise in the background
loop {
let next_cmd = self.command_receiver_channel.recv().await.unwrap();
match next_cmd {
ClientCommands::RpcRequest(req) => {
if self.rpc_runner.has_active_request() {
match req.queue_type {
QueueTypes::UserKillRequest => {
let _ = self.rpc_runner.send_request_kill_signal().await;
// User requests can quickly change before being finished. If a user changes
// their request, then other user requests should just be cancelled.
if let Some(next_kill_req) = &self.active_kill_request_next {
let _ = next_kill_req.kill().await;
}
self.active_kill_request_next = Some(req);
}
QueueTypes::SystemQueueRequest => {
self.rpc_request_queue.push_back(req);
}
}
} else {
let _ = self.rpc_runner.send_request(req).await;
}
}
/* The rest of the matched types are also included here, but removed in this description */
}
}
After writing all of this, I realized this could have been simplified with a wrapping type that does all of this with closures. So, I took the runtime/server related stuff, and made a custom type, JsArc, that I think would help to reduce the complexity. In the example of RPC requests, the above could have been something like the following
let (s, r) = async_channel::unbounded();
let request: String = request;
self.jswallet.with_self_async(|jswallet| async move {
let result = jswallet.request(request).await;
s.send(result.to_string().unwrap()).await;
jswallet
})
.await;
let result: String = r.recv().await;
Though, I didn't fix the bindings abstraction because I had already written message sending and receiving for everything. If I were to write it again, I think I would just use that wrapped JsValue type instead of message passing to a custom runtime/server.
The rest of the complexity is mostly associated with experimentation
npm
)
wasm-pack build --target web
cargo install miniserve
miniserve .
Miniserve starts a webserver in current directory. It will display a address to visit, then load the index.html file at that address.
cd js
npm install
cd bindings
npx webpack
You will need to have NPM and Webpack installed for the above to work.