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