feat: Add WASM support and browser extension infrastructure

- Add WASM build target and dependencies for all crates.
- Implement IndexedDB-based persistent storage for WASM.
- Create browser extension infrastructure (UI, scripting, etc.).
- Integrate Rhai scripting engine for secure automation.
- Implement user stories and documentation for the extension.
This commit is contained in:
2025-05-16 15:31:53 +03:00
parent 19f46d6edb
commit 13945a8725
25 changed files with 672 additions and 183 deletions

View File

@@ -7,6 +7,9 @@ edition = "2021"
path = "src/lib.rs"
[dependencies]
kvstore = { path = "../kvstore" }
tokio = { version = "1.37", features = ["rt", "macros"] }
rhai = "1.16"
ethers-core = "2.0"
gloo-net = { version = "0.5", features = ["http"] }
rlp = "0.5"
@@ -23,10 +26,16 @@ hex = "0.4"
k256 = { version = "0.13", features = ["ecdsa"] }
[dev-dependencies]
tempfile = "3.10"
wasm-bindgen-test = "0.3"
web-sys = { version = "0.3", features = ["console"] }
tempfile = "3"
kvstore = { path = "../kvstore" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.3", features = ["wasm_js"] }
getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] }
wasm-bindgen = "0.2"
js-sys = "0.3"
console_error_panic_hook = "0.1"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

View File

@@ -14,4 +14,22 @@
pub use ethers_core::types::*;
pub mod provider;
pub mod rhai_bindings;
pub mod rhai_sync_helpers;
pub use provider::send_rpc;
/// Public EVM client struct for use in bindings and sync helpers
pub struct EvmClient {
// Add fields as needed for your implementation
}
impl EvmClient {
pub async fn get_balance(&self, provider_url: &str, public_key: &[u8]) -> Result<u64, String> {
// TODO: Implement actual logic
Ok(0)
}
pub async fn send_transaction(&self, provider_url: &str, key_id: &str, password: &[u8], tx_data: rhai::Map) -> Result<String, String> {
// TODO: Implement actual logic
Ok("tx_hash_placeholder".to_string())
}
}

View File

@@ -0,0 +1,49 @@
//! Rhai bindings for EVM Client module
//! Provides a single source of truth for scripting integration for EVM actions.
use rhai::{Engine, Map};
pub use crate::EvmClient; // Ensure EvmClient is public and defined in lib.rs
/// Register EVM Client APIs with the Rhai scripting engine.
#[cfg(not(target_arch = "wasm32"))]
pub fn register_rhai_api(engine: &mut Engine, evm_client: std::sync::Arc<EvmClient>) {
/// Rhai-friendly wrapper for EvmClient, allowing method registration and instance sharing.
#[derive(Clone)]
struct RhaiEvmClient {
inner: std::sync::Arc<EvmClient>,
}
impl RhaiEvmClient {
/// Get balance using the EVM client.
pub fn get_balance(&self, provider_url: String, public_key: rhai::Blob) -> Result<String, String> {
// Use the sync helper from crate::rhai_sync_helpers
crate::rhai_sync_helpers::get_balance_sync(&self.inner, &provider_url, &public_key)
}
/// Send transaction using the EVM client.
pub fn send_transaction(&self, provider_url: String, key_id: String, password: rhai::Blob, tx_data: rhai::Map) -> Result<String, String> {
// Use the sync helper from crate::rhai_sync_helpers
crate::rhai_sync_helpers::send_transaction_sync(&self.inner, &provider_url, &key_id, &password, tx_data)
}
}
engine.register_type::<RhaiEvmClient>();
engine.register_fn("get_balance", RhaiEvmClient::get_balance);
engine.register_fn("send_transaction", RhaiEvmClient::send_transaction);
// Register instance for scripts
let rhai_ec = RhaiEvmClient { inner: evm_client.clone() };
// Rhai does not support register_global_constant; pass the client as a parameter or use module scope.
}
#[cfg(target_arch = "wasm32")]
pub fn register_rhai_api(engine: &mut Engine) {
// In WASM, register global functions that operate on the singleton/global EvmClient
engine.register_fn("get_balance", |provider_url: String, public_key: rhai::Blob| -> Result<String, String> {
// WASM: get_balance is async, so error if called from Rhai
Err("get_balance is async in WASM; use the WASM get_balance() API from JS instead".to_string())
});
engine.register_fn("send_transaction", |provider_url: String, key_id: String, password: rhai::Blob, tx_data: rhai::Map| -> Result<String, String> {
// WASM: send_transaction is async, so error if called from Rhai
Err("send_transaction is async in WASM; use the WASM send_transaction() API from JS instead".to_string())
});
// No global evm object in WASM; use JS/WASM API for EVM ops
}

View File

@@ -0,0 +1,40 @@
//! Synchronous wrappers for async EVM client APIs for use in Rhai bindings.
//! These use block_on for native, and should be adapted for WASM as needed.
use crate::EvmClient;
use rhai::Map;
#[cfg(not(target_arch = "wasm32"))]
use tokio::runtime::Handle;
/// Synchronously get the balance using the EVM client.
#[cfg(not(target_arch = "wasm32"))]
pub fn get_balance_sync(
evm_client: &EvmClient,
provider_url: &str,
public_key: &[u8],
) -> Result<String, String> {
Handle::current().block_on(async {
evm_client.get_balance(provider_url, public_key)
.await
.map(|b| b.to_string())
.map_err(|e| format!("get_balance error: {e}"))
})
}
/// Synchronously send a transaction using the EVM client.
#[cfg(not(target_arch = "wasm32"))]
pub fn send_transaction_sync(
evm_client: &EvmClient,
provider_url: &str,
key_id: &str,
password: &[u8],
tx_data: Map,
) -> Result<String, String> {
Handle::current().block_on(async {
evm_client.send_transaction(provider_url, key_id, password, tx_data)
.await
.map(|tx| tx.to_string())
.map_err(|e| format!("send_transaction error: {e}"))
})
}

View File

@@ -1,17 +1,20 @@
use evm_client::provider::Transaction;
use evm_client::provider::{parse_signature_rs_v, get_balance};
use ethers_core::types::{Address, U256};
// This file contains native-only integration tests for EVM client balance and signing logic.
// All code is strictly separated from WASM code using cfg attributes.
#[cfg(not(target_arch = "wasm32"))]
mod native_tests {
use super::*;
use vault::{SessionManager, KeyType};
use tempfile::TempDir;
use kvstore::native::NativeStore;
use alloy_primitives::keccak256;
#[tokio::test]
async fn test_vault_sessionmanager_balance_for_new_keypair() {
use ethers_core::types::{Address, U256};
use evm_client::provider::get_balance;
let tmp_dir = TempDir::new().expect("create temp dir");
let store = NativeStore::open(tmp_dir.path().to_str().unwrap()).expect("Failed to open native store");
let mut vault = vault::Vault::new(store.clone());
@@ -40,11 +43,13 @@ mod native_tests {
}
}
use ethers_core::types::Bytes;
use ethers_core::types::Bytes;
#[test]
fn test_rlp_encode_unsigned() {
fn test_rlp_encode_unsigned() {
use ethers_core::types::{Address, U256, Bytes};
use evm_client::provider::Transaction;
let tx = Transaction {
nonce: U256::from(1),
to: Address::zero(),
@@ -59,7 +64,10 @@ fn test_rlp_encode_unsigned() {
}
#[test]
fn test_parse_signature_rs_v() {
fn test_parse_signature_rs_v() {
use ethers_core::types::U256;
use evm_client::provider::parse_signature_rs_v;
let mut sig = [0u8; 65];
sig[31] = 1; sig[63] = 2; sig[64] = 27;
let (r, s, v) = parse_signature_rs_v(&sig, 1).unwrap();
@@ -70,7 +78,10 @@ fn test_parse_signature_rs_v() {
#[cfg(not(target_arch = "wasm32"))]
#[tokio::test]
async fn test_get_balance_real_address() {
async fn test_get_balance_real_address() {
use ethers_core::types::{Address, U256};
use evm_client::provider::get_balance;
// Vitalik's address
let address = "d8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
let address = ethers_core::types::Address::from_slice(&hex::decode(address).unwrap());

View File

@@ -1,3 +1,5 @@
// This file contains WASM-only integration tests for EVM client balance and signing logic.
// All code is strictly separated from native using cfg attributes.
#![cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);