#modal #bindings #ethereum #wasm #wallet-connect

已删除 bindings-abstraction-wc-modal

WalletConnect Modal 绑定的 WASM 抽象

1 个不稳定版本

0.1.0 2024年5月24日

#21 in #modal

MIT 许可证

2MB
9K SLoC

Rust 4.5K SLoC // 0.1% comments JavaScript 4K SLoC // 0.0% comments

WalletConnect Modal 绑定的 WASM 抽象

这是为 WalletConnect 的 Modal 创建的绑定抽象。使用 cargo doc --target wasm32-unknown-unknown --open 可以提供有关使用此库的信息。

Usage

为什么会有这个存在呢?

使用 WalletConnect 与 Rust WASM 前端似乎很复杂,我想为我的项目获取绑定。

这个抽象的目标仅仅是使 Rust 基于以太坊/区块链提供者(如 ethers-rs、alloy-rs、web3-rs 等)轻松使用 WalletConnect 的 Modal。尽管目前这个包仅支持 ethers-rs。这已经有一段时间没有写了,所以 NPM 中的包也不是新的。(我也有一段时间没有搜索可能具有相同用途的其他库了)

为什么要以这种方式编写呢?

我用编写这个库来尝试 Rust。

编写它的一个目标是将 JS 中的动态类型转换为 Rust 中的可靠和稳固的类型。我认为它在内部过于复杂,我最初尝试封装 JsValue 的方法难以理解。

包含内部 JsValue 值的结构无法直接与 ethers、alloy 和其他提供者的 trait 要求进行交互,因为它们需要 Send + Sync 限制。在没有现有运行时的情况下,一个安全的 Rust 解决方案是使用异步通道库和运行时/服务器来监听这些消息。这正是这个包所做的事情,它充当运行时/服务器,并将提供者请求发送的消息传递给从基于 Rust 的提供者传递的 trait 限制。

当前版本执行对 JS 绑定 RPC 提供者的 JSON 请求看起来如下:

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),
    });
}

这有一个相关的运行时/服务器,它作为后台中的 promise 运行。

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 */
        }
}

在写完所有这些后,我意识到这可以通过一个包装类型来实现简化,该类型使用闭包完成所有这些操作。因此,我提取了与运行时/服务器相关的内容,创建了一个自定义类型 JsArc,我认为这将有助于降低复杂性。在RPC请求的例子中,上述内容可能如下所示:

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;

尽管如此,我没有修复绑定抽象,因为我已经为所有内容编写了消息发送和接收。如果我现在再写一遍,我认为我会直接使用那个包装的JsValue类型,而不是将消息传递到自定义运行时/服务器。

其余的复杂性主要与实验相关

  • 我尝试编写宏,因为提供者必须手动克隆才能移动到自定义future
  • 我尝试创建队列,因为我感觉这可能是一个很好的步骤,未来库可以使用它来知道何时取消钱包的请求
  • 我尝试创建rust提供者错误的共享格式
  • 我希望下游用户能够选择不手动管理NPM依赖项,因此这个crate使用了一个预捆绑的版本。(但用户也有选择使用带有功能标志 npm 的npm)
    • 从web链接模块到动态模块的更改导致原始绑定不匹配。(并且留下了旧绑定的部分)

构建

使用示例

wasm-pack build --target web
cargo install miniserve
miniserve .

Miniserve在当前目录启动一个web服务器。它将显示一个要访问的地址,然后在该地址加载index.html文件。

构建新的web3_bindings.bundle.js

cd js
npm install
cd bindings
npx webpack

您需要安装NPM和Webpack才能使上述操作正常工作。

依赖项

~35–52MB
~1M SLoC