feat: implement browser extension UI with WebAssembly integration
This commit is contained in:
		| @@ -7,20 +7,16 @@ edition = "2021" | ||||
| path = "src/lib.rs" | ||||
|  | ||||
| [dependencies] | ||||
| kvstore = { path = "../kvstore" } | ||||
| # Only universal/core dependencies here | ||||
|  | ||||
| tokio = { version = "1.37", features = ["rt", "macros"] } | ||||
| rhai = "1.16" | ||||
| ethers-core = "2.0" | ||||
| gloo-net = { version = "0.5", features = ["http"] } | ||||
| rlp = "0.5" | ||||
| reqwest = { version = "0.11", features = ["json"] } | ||||
| async-trait = "0.1" | ||||
| serde = { version = "1", features = ["derive"] } | ||||
| serde_json = "1" | ||||
| vault = { path = "../vault" } | ||||
| thiserror = "1" | ||||
| alloy-rlp = { version = "0.3.11", features = ["derive"] } | ||||
| alloy-primitives = "1.1.0" | ||||
| log = "0.4" | ||||
| hex = "0.4" | ||||
| k256 = { version = "0.13", features = ["ecdsa"] } | ||||
| @@ -32,10 +28,17 @@ web-sys = { version = "0.3", features = ["console"] } | ||||
|  | ||||
| [target.'cfg(target_arch = "wasm32")'.dependencies] | ||||
| getrandom = { version = "0.3", features = ["wasm_js"] } | ||||
| getrandom_02 = { package = "getrandom", version = "0.2", features = ["js"] } | ||||
| getrandom_02 = { package = "getrandom", version = "0.2.16", features = ["js"] } | ||||
| wasm-bindgen = "0.2" | ||||
| js-sys = "0.3" | ||||
| console_error_panic_hook = "0.1" | ||||
| # console_error_panic_hook = "0.1" | ||||
| gloo-net = { version = "0.5", features = ["http"] } | ||||
| console_log = "1" | ||||
|  | ||||
| [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] | ||||
| tokio = { version = "1", features = ["macros", "rt-multi-thread"] } | ||||
| log = "0.4" | ||||
|  | ||||
| [target.'cfg(not(target_arch = "wasm32"))'.dependencies] | ||||
| env_logger = "0.11" | ||||
| reqwest = { version = "0.11", features = ["json"] } | ||||
|   | ||||
							
								
								
									
										9
									
								
								evm_client/src/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								evm_client/src/error.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #[derive(Debug, thiserror::Error)] | ||||
| pub enum EvmError { | ||||
|     #[error("RPC error: {0}")] | ||||
|     Rpc(String), | ||||
|     #[error("Signing error: {0}")] | ||||
|     Signing(String), | ||||
|     #[error("Other error: {0}")] | ||||
|     Other(String), | ||||
| } | ||||
| @@ -14,22 +14,151 @@ | ||||
|  | ||||
| pub use ethers_core::types::*; | ||||
| pub mod provider; | ||||
| pub mod signer; | ||||
| pub mod rhai_bindings; | ||||
| pub mod rhai_sync_helpers; | ||||
| pub mod error; | ||||
| pub use provider::send_rpc; | ||||
| pub use error::EvmError; | ||||
|  | ||||
| /// Public EVM client struct for use in bindings and sync helpers | ||||
| pub struct Provider { | ||||
|     pub rpc_url: String, | ||||
|     pub chain_id: u64, | ||||
|     pub explorer_url: Option<String>, | ||||
| } | ||||
|  | ||||
| pub struct EvmClient { | ||||
|     // Add fields as needed for your implementation | ||||
|     pub provider: Provider, | ||||
| } | ||||
|  | ||||
| impl EvmClient { | ||||
|     pub async fn get_balance(&self, provider_url: &str, public_key: &[u8]) -> Result<u64, String> { | ||||
|         // TODO: Implement actual logic | ||||
|         Ok(0) | ||||
|     pub fn new(provider: Provider) -> Self { | ||||
|         Self { provider } | ||||
|     } | ||||
|     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()) | ||||
|  | ||||
|     /// Initialize logging for the current target (native: env_logger, WASM: console_log) | ||||
|     /// Call this before using any log macros. | ||||
|     pub fn init_logging() { | ||||
|         use std::sync::Once; | ||||
|         static INIT: Once = Once::new(); | ||||
|         INIT.call_once(|| { | ||||
|             #[cfg(not(target_arch = "wasm32"))] | ||||
|             { | ||||
|                 use env_logger; | ||||
|                 let _ = env_logger::builder().is_test(false).try_init(); | ||||
|             } | ||||
|             #[cfg(target_arch = "wasm32")] | ||||
|             { | ||||
|                 use console_log; | ||||
|                 let _ = console_log::init_with_level(log::Level::Debug); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     pub async fn get_balance(&self, address: Address) -> Result<U256, EvmError> { | ||||
|         // TODO: Use provider info | ||||
|         provider::get_balance(&self.provider.rpc_url, address) | ||||
|             .await | ||||
|             .map_err(|e| EvmError::Rpc(e.to_string())) | ||||
|     } | ||||
|  | ||||
|     pub async fn send_transaction( | ||||
|         &self, | ||||
|         mut tx: provider::Transaction, | ||||
|         signer: &dyn crate::signer::Signer, | ||||
|     ) -> Result<ethers_core::types::H256, EvmError> { | ||||
|         use ethers_core::types::{U256, H256, Bytes, Address}; | ||||
|         use std::str::FromStr; | ||||
|         use serde_json::json; | ||||
|         use crate::provider::{send_rpc, parse_signature_rs_v}; | ||||
|  | ||||
|         // 1. Fill in missing fields via JSON-RPC if needed | ||||
|         // Parse signer address as H160 | ||||
|         let signer_addr = ethers_core::types::Address::from_str(&signer.address()) | ||||
|             .map_err(|e| EvmError::Rpc(format!("Invalid signer address: {}", e)))?; | ||||
|         // Nonce | ||||
|         if tx.nonce.is_none() { | ||||
|             let body = json!({ | ||||
|                 "jsonrpc": "2.0", | ||||
|                 "method": "eth_getTransactionCount", | ||||
|                 "params": [format!("0x{:x}", signer_addr), "pending"], | ||||
|                 "id": 1 | ||||
|             }).to_string(); | ||||
|             let resp = send_rpc(&self.provider.rpc_url, &body).await.map_err(|e| EvmError::Rpc(e.to_string()))?; | ||||
|             let v: serde_json::Value = serde_json::from_str(&resp).map_err(|e| EvmError::Rpc(e.to_string()))?; | ||||
|             let hex = v["result"].as_str().ok_or_else(|| EvmError::Rpc("No result field in eth_getTransactionCount".to_string()))?; | ||||
|             tx.nonce = Some(U256::from_str_radix(hex.trim_start_matches("0x"), 16).map_err(|e| EvmError::Rpc(e.to_string()))?); | ||||
|         } | ||||
|         // Gas Price | ||||
|         if tx.gas_price.is_none() { | ||||
|             let body = json!({ | ||||
|                 "jsonrpc": "2.0", | ||||
|                 "method": "eth_gasPrice", | ||||
|                 "params": [], | ||||
|                 "id": 1 | ||||
|             }).to_string(); | ||||
|             let resp = send_rpc(&self.provider.rpc_url, &body).await.map_err(|e| EvmError::Rpc(e.to_string()))?; | ||||
|             let v: serde_json::Value = serde_json::from_str(&resp).map_err(|e| EvmError::Rpc(e.to_string()))?; | ||||
|             let hex = v["result"].as_str().ok_or_else(|| EvmError::Rpc("No result field in eth_gasPrice".to_string()))?; | ||||
|             tx.gas_price = Some(U256::from_str_radix(hex.trim_start_matches("0x"), 16).map_err(|e| EvmError::Rpc(e.to_string()))?); | ||||
|         } | ||||
|         // Chain ID | ||||
|         if tx.chain_id.is_none() { | ||||
|             tx.chain_id = Some(self.provider.chain_id); | ||||
|         } | ||||
|         // Gas (optional: estimate if missing) | ||||
|         if tx.gas.is_none() { | ||||
|             let body = json!({ | ||||
|                 "jsonrpc": "2.0", | ||||
|                 "method": "eth_estimateGas", | ||||
|                 "params": [{ | ||||
|                     "to": format!("0x{:x}", tx.to), | ||||
|                     "from": format!("0x{:x}", signer_addr), | ||||
|                     "value": format!("0x{:x}", tx.value), | ||||
|                     "data": format!("0x{}", hex::encode(&tx.data)), | ||||
|                 }], | ||||
|                 "id": 1 | ||||
|             }).to_string(); | ||||
|             let resp = send_rpc(&self.provider.rpc_url, &body).await.map_err(|e| EvmError::Rpc(e.to_string()))?; | ||||
|             let v: serde_json::Value = serde_json::from_str(&resp).map_err(|e| EvmError::Rpc(e.to_string()))?; | ||||
|             let hex = v["result"].as_str().ok_or_else(|| EvmError::Rpc("No result field in eth_estimateGas".to_string()))?; | ||||
|             tx.gas = Some(U256::from_str_radix(hex.trim_start_matches("0x"), 16).map_err(|e| EvmError::Rpc(e.to_string()))?); | ||||
|         } | ||||
|  | ||||
|         // 2. RLP encode unsigned transaction | ||||
|         let rlp_unsigned = tx.rlp_encode_unsigned(); | ||||
|  | ||||
|         // 3. Sign the RLP-encoded unsigned transaction | ||||
|         let sig = signer.sign(&rlp_unsigned).await?; | ||||
|         let (r, s, v) = parse_signature_rs_v(&sig, tx.chain_id.unwrap()).ok_or_else(|| EvmError::Signing("Invalid signature format".to_string()))?; | ||||
|  | ||||
|         // 4. RLP encode signed transaction (EIP-155) | ||||
|         use rlp::RlpStream; | ||||
|         let mut rlp_stream = RlpStream::new_list(9); | ||||
|         rlp_stream.append(&tx.nonce.unwrap()); | ||||
|         rlp_stream.append(&tx.gas_price.unwrap()); | ||||
|         rlp_stream.append(&tx.gas.unwrap()); | ||||
|         rlp_stream.append(&tx.to); | ||||
|         rlp_stream.append(&tx.value); | ||||
|         rlp_stream.append(&tx.data.to_vec()); | ||||
|         rlp_stream.append(&tx.chain_id.unwrap()); | ||||
|         rlp_stream.append(&r); | ||||
|         rlp_stream.append(&s); | ||||
|         let raw_tx = rlp_stream.out().to_vec(); | ||||
|  | ||||
|         // 5. Broadcast the raw transaction | ||||
|         let raw_hex = format!("0x{}", hex::encode(&raw_tx)); | ||||
|         let body = json!({ | ||||
|             "jsonrpc": "2.0", | ||||
|             "method": "eth_sendRawTransaction", | ||||
|             "params": [raw_hex], | ||||
|             "id": 1 | ||||
|         }).to_string(); | ||||
|         let resp = send_rpc(&self.provider.rpc_url, &body).await.map_err(|e| EvmError::Rpc(e.to_string()))?; | ||||
|         let v: serde_json::Value = serde_json::from_str(&resp).map_err(|e| EvmError::Rpc(e.to_string()))?; | ||||
|         let tx_hash_hex = v["result"].as_str().ok_or_else(|| EvmError::Rpc("No result field in eth_sendRawTransaction".to_string()))?; | ||||
|         let tx_hash = H256::from_slice(&hex::decode(tx_hash_hex.trim_start_matches("0x")).map_err(|e| EvmError::Rpc(e.to_string()))?); | ||||
|         Ok(tx_hash) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -30,13 +30,13 @@ pub async fn send_rpc(url: &str, body: &str) -> Result<String, Box<dyn Error>> { | ||||
|     } | ||||
| } | ||||
| pub struct Transaction { | ||||
|     pub nonce: U256, | ||||
|     pub to: Address, | ||||
|     pub value: U256, | ||||
|     pub gas: U256, | ||||
|     pub gas_price: U256, | ||||
|     pub data: Bytes, | ||||
|     pub chain_id: u64, | ||||
|     pub gas: Option<U256>, | ||||
|     pub gas_price: Option<U256>, | ||||
|     pub nonce: Option<U256>, | ||||
|     pub chain_id: Option<u64>, | ||||
| } | ||||
|  | ||||
| impl Transaction { | ||||
| @@ -94,5 +94,4 @@ pub async fn get_balance(url: &str, address: Address) -> Result<U256, Box<dyn st | ||||
|     Ok(balance) | ||||
| } | ||||
|  | ||||
| // (Remove old sign_and_serialize placeholder) | ||||
|  | ||||
|   | ||||
| @@ -14,20 +14,16 @@ pub fn register_rhai_api(engine: &mut Engine, evm_client: std::sync::Arc<EvmClie | ||||
|     } | ||||
|     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) | ||||
|         pub fn get_balance(&self, address_hex: String) -> Result<String, String> { | ||||
|             use ethers_core::types::Address; | ||||
|             let address = Address::from_slice(&hex::decode(address_hex.trim_start_matches("0x")).map_err(|e| format!("hex decode error: {e}"))?); | ||||
|             crate::rhai_sync_helpers::get_balance_sync(&self.inner, address) | ||||
|         } | ||||
|  | ||||
|         /// 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. | ||||
|   | ||||
| @@ -11,11 +11,10 @@ use tokio::runtime::Handle; | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| pub fn get_balance_sync( | ||||
|     evm_client: &EvmClient, | ||||
|     provider_url: &str, | ||||
|     public_key: &[u8], | ||||
|     address: ethers_core::types::Address, | ||||
| ) -> Result<String, String> { | ||||
|     Handle::current().block_on(async { | ||||
|         evm_client.get_balance(provider_url, public_key) | ||||
|         evm_client.get_balance(address) | ||||
|             .await | ||||
|             .map(|b| b.to_string()) | ||||
|             .map_err(|e| format!("get_balance error: {e}")) | ||||
| @@ -26,15 +25,13 @@ pub fn get_balance_sync( | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| pub fn send_transaction_sync( | ||||
|     evm_client: &EvmClient, | ||||
|     provider_url: &str, | ||||
|     key_id: &str, | ||||
|     password: &[u8], | ||||
|     tx_data: Map, | ||||
|     tx: crate::provider::Transaction, | ||||
|     signer: &dyn crate::signer::Signer, | ||||
| ) -> Result<String, String> { | ||||
|     Handle::current().block_on(async { | ||||
|         evm_client.send_transaction(provider_url, key_id, password, tx_data) | ||||
|         evm_client.send_transaction(tx, signer) | ||||
|             .await | ||||
|             .map(|tx| tx.to_string()) | ||||
|             .map(|tx| format!("0x{:x}", tx)) | ||||
|             .map_err(|e| format!("send_transaction error: {e}")) | ||||
|     }) | ||||
| } | ||||
|   | ||||
| @@ -1,80 +1,8 @@ | ||||
| // Signing should be done using ethers-core utilities directly. This file is now empty. | ||||
| use super::error::EvmError; | ||||
|  | ||||
| // Native: Only compile for non-WASM | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| #[async_trait] | ||||
| #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] | ||||
| #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] | ||||
| pub trait Signer: Send + Sync { | ||||
|     async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, EvmError>; | ||||
|     fn address(&self) -> String; | ||||
| } | ||||
|  | ||||
| // WASM: Only compile for WASM | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| #[async_trait(?Send)] | ||||
| pub trait Signer { | ||||
|     async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, EvmError>; | ||||
|     fn address(&self) -> String; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| // --- Implementation for vault::SessionManager --- | ||||
|  | ||||
| #[cfg(target_arch = "wasm32")] | ||||
| #[async_trait::async_trait(?Send)] | ||||
| impl<S: vault::KVStore> Signer for vault::SessionManager<S> { | ||||
|     async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, EvmError> { | ||||
|         log::debug!("SessionManager::sign called"); | ||||
|         self.sign(message) | ||||
|             .await | ||||
|             .map_err(|e| { | ||||
|                 log::error!("Vault signing error: {}", e); | ||||
|                 EvmError::Vault(e.to_string()) | ||||
|             }) | ||||
|     } | ||||
|     fn address(&self) -> String { | ||||
|         log::debug!("SessionManager::address called"); | ||||
|         self.current_keypair() | ||||
|             .map(|k| { | ||||
|                 if k.key_type == vault::KeyType::Secp256k1 { | ||||
|                     let pubkey = &k.public_key; | ||||
|                     use alloy_primitives::keccak256; | ||||
|                     let hash = keccak256(&pubkey[1..]); | ||||
|                     format!("0x{}", hex::encode(&hash[12..])) | ||||
|                 } else { | ||||
|                     format!("0x{}", hex::encode(&k.public_key)) | ||||
|                 } | ||||
|             }) | ||||
|             .unwrap_or_default() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(not(target_arch = "wasm32"))] | ||||
| #[async_trait::async_trait] | ||||
|  | ||||
| impl<S: vault::KVStore + Send + Sync> Signer for vault::SessionManager<S> { | ||||
|     async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, EvmError> { | ||||
|         log::debug!("SessionManager::sign called"); | ||||
|         self.sign(message) | ||||
|             .await | ||||
|             .map_err(|e| { | ||||
|                 log::error!("Vault signing error: {}", e); | ||||
|                 EvmError::Vault(e.to_string()) | ||||
|             }) | ||||
|     } | ||||
|     fn address(&self) -> String { | ||||
|         log::debug!("SessionManager::address called"); | ||||
|         self.current_keypair() | ||||
|             .map(|k| { | ||||
|                 if k.key_type == vault::KeyType::Secp256k1 { | ||||
|                     let pubkey = &k.public_key; | ||||
|                     use alloy_primitives::keccak256; | ||||
|                     let hash = keccak256(&pubkey[1..]); | ||||
|                     format!("0x{}", hex::encode(&hash[12..])) | ||||
|                 } else { | ||||
|                     format!("0x{}", hex::encode(&k.public_key)) | ||||
|                 } | ||||
|             }) | ||||
|             .unwrap_or_default() | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,63 +1,21 @@ | ||||
| // 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 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()); | ||||
|         let keyspace = "testspace"; | ||||
|         let password = b"testpass"; | ||||
|         // 1. Create keyspace | ||||
|         vault.create_keyspace(keyspace, password, None).await.expect("create keyspace"); | ||||
|         // 2. Add secp256k1 keypair | ||||
|         let key_id = vault.add_keypair(keyspace, password, Some(KeyType::Secp256k1), None).await.expect("add keypair"); | ||||
|         // 3. Create SessionManager and unlock keyspace | ||||
|         let mut session = SessionManager::new(vault); | ||||
|         session.unlock_keyspace(keyspace, password).await.expect("unlock keyspace"); | ||||
|         session.select_keyspace(keyspace).expect("select keyspace"); | ||||
|         session.select_keypair(&key_id).expect("select keypair"); | ||||
|         let kp = session.current_keypair().expect("current keypair"); | ||||
|         // 4. Derive Ethereum address from public key (same as in evm_client) | ||||
|         let pubkey = &kp.public_key; | ||||
|         // Remove leading 0x04 if present (uncompressed SEC1) | ||||
|         let pubkey = if pubkey.len() == 65 && pubkey[0] == 0x04 { &pubkey[1..] } else { pubkey.as_slice() }; | ||||
|         let hash = keccak256(pubkey); | ||||
|         let address = Address::from_slice(&hash[12..]); | ||||
|         // 5. Query balance | ||||
|         let url = "https://ethereum.blockpi.network/v1/rpc/public"; | ||||
|         let balance = get_balance(url, address).await.expect("Failed to get balance"); | ||||
|         assert_eq!(balance, ethers_core::types::U256::zero(), "New keypair should have zero balance"); | ||||
|     } | ||||
| } | ||||
|  | ||||
|     use ethers_core::types::Bytes; | ||||
|  | ||||
| #[test] | ||||
|     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(), | ||||
|         value: U256::from(100), | ||||
|         gas: U256::from(21000), | ||||
|         gas_price: U256::from(1), | ||||
|         data: Bytes::new(), | ||||
|         chain_id: 1u64, | ||||
|         gas: Some(U256::from(21000)), | ||||
|         gas_price: Some(U256::from(1)), | ||||
|         nonce: Some(U256::from(1)), | ||||
|         chain_id: Some(1u64), | ||||
|     }; | ||||
|     let rlp = tx.rlp_encode_unsigned(); | ||||
|     assert!(!rlp.is_empty()); | ||||
| @@ -86,6 +44,6 @@ mod native_tests { | ||||
|     let address = "d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; | ||||
|     let address = ethers_core::types::Address::from_slice(&hex::decode(address).unwrap()); | ||||
|     let url = "https://ethereum.blockpi.network/v1/rpc/public"; | ||||
|     let balance = get_balance(url, address).await.expect("Failed to get balance"); | ||||
|     let balance = get_balance(url, address).await.expect("Failed to get balance"); // TODO: Update to use new EvmClient API | ||||
|     assert!(balance > ethers_core::types::U256::zero(), "Vitalik's balance should be greater than zero"); | ||||
| } | ||||
|   | ||||
| @@ -11,13 +11,13 @@ use hex; | ||||
| #[wasm_bindgen_test] | ||||
| fn test_rlp_encode_unsigned() { | ||||
|     let tx = Transaction { | ||||
|         nonce: U256::from(1), | ||||
|         to: Address::zero(), | ||||
|         value: U256::from(100), | ||||
|         gas: U256::from(21000), | ||||
|         gas_price: U256::from(1), | ||||
|         data: Bytes::new(), | ||||
|         chain_id: 1, | ||||
|         gas: Some(U256::from(21000)), | ||||
|         gas_price: Some(U256::from(1)), | ||||
|         nonce: Some(U256::from(1)), | ||||
|         chain_id: Some(1), | ||||
|     }; | ||||
|     let rlp = tx.rlp_encode_unsigned(); | ||||
|     assert!(!rlp.is_empty()); | ||||
| @@ -31,44 +31,11 @@ pub async fn test_get_balance_real_address_wasm_unique() { | ||||
|     let address = "d8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; | ||||
|     let address = Address::from_slice(&hex::decode(address).unwrap()); | ||||
|     let url = "https://ethereum.blockpi.network/v1/rpc/public"; | ||||
|     let balance = get_balance(url, address).await.expect("Failed to get balance"); | ||||
|     let balance = get_balance(url, address).await.expect("Failed to get balance"); // TODO: Update to use new EvmClient API | ||||
|     web_sys::console::log_1(&format!("Balance: {balance:?}").into()); | ||||
|     assert!(balance > U256::zero(), "Vitalik's balance should be greater than zero"); | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen_test(async)] | ||||
| pub async fn test_vault_sessionmanager_balance_for_new_keypair_wasm() { | ||||
|     use vault::{SessionManager, KeyType}; | ||||
|     use ethers_core::types::Address; | ||||
|     use evm_client::provider::get_balance; | ||||
|     use alloy_primitives::keccak256; | ||||
|     web_sys::console::log_1(&"WASM vault-session balance test running!".into()); | ||||
|     let store = kvstore::wasm::WasmStore::open("test-db").await.expect("open"); | ||||
|     let mut vault = vault::Vault::new(store); | ||||
|     let keyspace = "testspace-wasm"; | ||||
|     let password = b"testpass"; | ||||
|     // 1. Create keyspace | ||||
|     vault.create_keyspace(keyspace, password, None).await.expect("create keyspace"); | ||||
|     // 2. Add secp256k1 keypair | ||||
|     let key_id = vault.add_keypair(keyspace, password, Some(KeyType::Secp256k1), None).await.expect("add keypair"); | ||||
|     // 3. Create SessionManager and unlock keyspace | ||||
|     let mut session = SessionManager::new(vault); | ||||
|     session.unlock_keyspace(keyspace, password).await.expect("unlock keyspace"); | ||||
|     session.select_keyspace(keyspace).expect("select keyspace"); | ||||
|     session.select_keypair(&key_id).expect("select keypair"); | ||||
|     let kp = session.current_keypair().expect("current keypair"); | ||||
|     // 4. Derive Ethereum address from public key | ||||
|     let pubkey = &kp.public_key; | ||||
|     let pubkey = if pubkey.len() == 65 && pubkey[0] == 0x04 { &pubkey[1..] } else { pubkey.as_slice() }; | ||||
|     let hash = keccak256(pubkey); | ||||
|     let address = Address::from_slice(&hash[12..]); | ||||
|     // 5. Query balance | ||||
|     let url = "https://ethereum.blockpi.network/v1/rpc/public"; | ||||
|     let balance = get_balance(url, address).await.expect("Failed to get balance"); | ||||
|     web_sys::console::log_1(&format!("Balance: {balance:?}").into()); | ||||
|     assert_eq!(balance, ethers_core::types::U256::zero(), "New keypair should have zero balance"); | ||||
| } | ||||
|  | ||||
| #[wasm_bindgen_test] | ||||
| fn test_parse_signature_rs_v() { | ||||
|     let mut sig = [0u8; 65]; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user