use std::str::FromStr;
use alloy::{
hex,
primitives::{Address, Bytes, FixedBytes, PrimitiveSignature, U256},
};
use console_error_panic_hook;
use js_sys::Date;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::future_to_promise;
use crate::middleware::{
one_time_payment::{
types::{OneTimePaymentConfig, SignedPaymentTx},
verify::verify_tx,
},
payment_channel::{
channel::{close_channel, ChannelState},
types::{PaymentChannel, PaymentChannelConfig, SignedRequest},
verify::{verify_and_update_channel, verify_channel},
},
stream_payment::{
state::StreamState,
types::{SignedStream, StreamListenerConfig, StreamsConfig},
verify::verify_stream,
Stream, StreamListner,
},
};
#[wasm_bindgen(start)]
fn start() {
console_error_panic_hook::set_once();
console_log::init().expect("error initializing log");
}
#[wasm_bindgen]
pub struct PaymentChannelVerifier {
inner: ChannelState,
config: PaymentChannelConfig,
}
#[wasm_bindgen]
impl PaymentChannelVerifier {
#[wasm_bindgen(constructor)]
pub fn new(config_json: String) -> Result<PaymentChannelVerifier, JsError> {
let config: PaymentChannelConfig = serde_json::from_str(&config_json)
.map_err(|e| JsError::new(&format!("Invalid channel config: {}", e)))?;
Ok(PaymentChannelVerifier {
inner: ChannelState::new(),
config: config,
})
}
#[wasm_bindgen]
pub fn verify_request(
&self,
message: String,
signature: String,
payment_channel_json: String,
payment_amount: u64,
timestamp: u64,
body_bytes: Vec<u8>,
) -> js_sys::Promise {
let state = self.inner.clone();
let config = self.config.clone();
future_to_promise(async move {
let message: Vec<u8> = unhexlify(&message)
.map_err(|e| JsValue::from_str(&format!("Invalid request: {}", e)))?;
let signature: PrimitiveSignature = unhexlify(&signature)
.map_err(|e| JsValue::from_str(&format!("Invalid signature: {}", e)))
.and_then(|bytes| {
PrimitiveSignature::try_from(bytes.as_slice())
.map_err(|_| JsValue::from_str("Invalid signature: invalid length"))
})?;
let payment_channel: PaymentChannel = serde_json::from_str(&payment_channel_json)
.map_err(|e| JsValue::from_str(&format!("Invalid payment channel: {}", e)))?;
let payment_amount = U256::from(payment_amount);
let request = SignedRequest {
message,
signature,
payment_channel,
payment_amount,
body_bytes,
timestamp,
};
let result = verify_and_update_channel(&state, &config, request)
.await
.map_err(|e| {
JsValue::from_str(&format!("Verification failed: {}", e.to_string()))
})?;
state
.channels
.write()
.await
.insert(result.0.channel_id, result.0.clone());
Ok(JsValue::from_str(&serde_json::to_string(&result).unwrap()))
})
}
}
#[wasm_bindgen]
pub fn verify_channel_no_state(
config_json: String,
current_channel_json: Option<String>,
message: String,
signature: String,
payment_channel_json: String,
payment_amount: u64,
timestamp: u64,
body_bytes: Vec<u8>,
) -> js_sys::Promise {
future_to_promise(async move {
let config: PaymentChannelConfig = serde_json::from_str(&config_json)
.map_err(|e| JsError::new(&format!("Invalid config: {}", e)))?;
let message: Vec<u8> = unhexlify(&message)
.map_err(|e| JsValue::from_str(&format!("Invalid request: {}", e)))?;
let signature: PrimitiveSignature = unhexlify(&signature)
.map_err(|e| JsValue::from_str(&format!("Invalid signature: {}", e)))
.and_then(|bytes| {
PrimitiveSignature::try_from(bytes.as_slice())
.map_err(|_| JsValue::from_str("Invalid signature: invalid length"))
})?;
let payment_channel: PaymentChannel = serde_json::from_str(&payment_channel_json)
.map_err(|e| JsValue::from_str(&format!("Invalid payment channel: {}", e)))?;
let current_channel: Option<PaymentChannel> = current_channel_json
.map(|json| {
serde_json::from_str(&json)
.map_err(|e| JsValue::from_str(&format!("Invalid current channel: {}", e)))
})
.transpose()?;
let payment_amount = U256::from(payment_amount);
let request = SignedRequest {
message,
signature,
payment_channel,
payment_amount,
body_bytes,
timestamp,
};
let result = verify_channel(config, request, current_channel)
.await
.map_err(|e| JsValue::from_str(&format!("Verification failed: {}", e.to_string())))?;
Ok(JsValue::from_str(&serde_json::to_string(&result).unwrap()))
})
}
#[wasm_bindgen]
pub fn verify_onetime_payment_tx(
ontime_payment_config_json: String,
signature: String,
tx_hash: String,
) -> js_sys::Promise {
future_to_promise(async move {
let onetime_payment_config: OneTimePaymentConfig =
serde_json::from_str(&ontime_payment_config_json).map_err(|e| {
JsValue::from_str(&format!("Invalid onetime payment config: {}", e))
})?;
let signature: PrimitiveSignature = unhexlify(&signature)
.map_err(|e| JsValue::from_str(&format!("Invalid signature: {}", e)))
.and_then(|bytes| {
PrimitiveSignature::try_from(bytes.as_slice())
.map_err(|_| JsValue::from_str("Invalid signature: invalid length"))
})?;
let tx_hash = FixedBytes::<32>::from_str(&tx_hash)
.map_err(|e| JsValue::from_str(&format!("Invalid transaction hash: {}", e)))?;
let signed_payment_tx = SignedPaymentTx { signature, tx_hash };
let (_, result) = verify_tx(signed_payment_tx, onetime_payment_config)
.await
.map_err(|e| JsValue::from_str(&format!("Verification failed: {}", e)))?;
Ok(JsValue::from_bool(result))
})
}
#[wasm_bindgen]
pub fn verify_stream_tx(
stream_config_json: String,
signature: String,
sender: String,
) -> js_sys::Promise {
future_to_promise(async move {
let stream_config: StreamsConfig = serde_json::from_str(&stream_config_json)
.map_err(|e| JsValue::from_str(&format!("Invalid stream config: {}", e)))?;
let signature: PrimitiveSignature = unhexlify(&signature)
.map_err(|e| JsValue::from_str(&format!("Invalid signature: {}", e)))
.and_then(|bytes| {
PrimitiveSignature::try_from(bytes.as_slice())
.map_err(|_| JsValue::from_str("Invalid signature: invalid length"))
})?;
let sender = Address::from_str(&sender)
.map_err(|e| JsValue::from_str(&format!("Invalid sender addres: {}", e)))?;
let signed_stream = SignedStream { signature, sender };
let result = verify_stream(signed_stream, stream_config)
.await
.map_err(|e| JsValue::from_str(&format!("Verification failed: {}", e)))?;
Ok(JsValue::from_bool(result))
})
}
#[wasm_bindgen]
pub struct StreamVerifier {
inner: StreamState,
config: StreamsConfig,
}
#[wasm_bindgen]
impl StreamVerifier {
#[wasm_bindgen(constructor)]
pub fn new(config_json: String) -> Result<StreamVerifier, JsError> {
let config: StreamsConfig = serde_json::from_str(&config_json)
.map_err(|e| JsError::new(&format!("Invalid stream config: {}", e)))?;
let state = StreamState::new();
Ok(StreamVerifier {
inner: state,
config: config,
})
}
#[wasm_bindgen]
pub fn start_listener(&self, listener_config_json: String) -> Result<(), JsError> {
let listener_config: StreamListenerConfig = serde_json::from_str(&listener_config_json)
.map_err(|e| JsError::new(&format!("Invalid stream listener config: {}", e)))?;
let _ = StreamListner::new(self.inner.clone(), self.config.clone(), listener_config);
Ok(())
}
#[wasm_bindgen]
pub fn verify_request(&self, signature: String, sender: String) -> js_sys::Promise {
let state = self.inner.clone();
let config = self.config.clone();
future_to_promise(async move {
let signature: PrimitiveSignature = unhexlify(&signature)
.map_err(|e| JsValue::from_str(&format!("Invalid signature: {}", e)))
.and_then(|bytes| {
PrimitiveSignature::try_from(bytes.as_slice())
.map_err(|_| JsValue::from_str("Invalid signature: invalid length"))
})?;
let sender = Address::from_str(&sender)
.map_err(|e| JsValue::from_str(&format!("Invalid sender addres: {}", e)))?;
let signed_stream = SignedStream { signature, sender };
if let Some(stream) = state.get(signed_stream.sender).await {
if stream.last_verified > 0 {
let timestamp = (Date::now() as u64) / 1000;
if timestamp - stream.last_verified < config.cache_time {
println!("Stream already verified, in Cache!");
println!("=== end middleware check ===");
return Ok(JsValue::from_bool(true));
}
}
}
let result = verify_stream(signed_stream.clone(), config.clone())
.await
.map_err(|e| JsValue::from_str(&format!("Verification failed: {}", e)))?;
if result {
println!("Verified");
println!("=== end middleware check ===");
let timestamp = (Date::now() as u64) / 1000;
let stream = Stream {
sender: signed_stream.sender,
recipient: config.recipient,
token_address: config.token_address,
flow_rate: config.amount,
last_verified: timestamp,
};
state.set(signed_stream.sender, stream).await;
}
Ok(JsValue::from_str(&serde_json::to_string(&result).unwrap()))
})
}
}
#[wasm_bindgen]
pub fn close_and_withdraw_channel(
rpc_url: String,
private_key: String,
signature: String,
payment_channel_json: String,
body_bytes: Vec<u8>,
) -> js_sys::Promise {
future_to_promise(async move {
let rpc_url: alloy::transports::http::reqwest::Url = rpc_url
.parse()
.map_err(|e| JsValue::from_str(&format!("Invalid URL: {}", e)))?;
let signature: PrimitiveSignature = unhexlify(&signature)
.map_err(|e| JsValue::from_str(&format!("Invalid signature: {}", e)))
.and_then(|bytes| {
PrimitiveSignature::try_from(bytes.as_slice())
.map_err(|_| JsValue::from_str("Invalid signature: invalid length"))
})?;
let payment_channel: PaymentChannel = serde_json::from_str(&payment_channel_json)
.map_err(|e| JsValue::from_str(&format!("Invalid payment channel: {}", e)))?;
close_channel(
rpc_url,
private_key.as_str(),
&payment_channel,
&signature,
Bytes::from(body_bytes),
)
.await
.map_err(|e| JsValue::from_str(&format!("Verification failed: {}", e)))?;
Ok(JsValue::NULL)
})
}
fn _hexlify(a: &[u8]) -> String {
let mut output = "0x".to_owned();
output.push_str(&hex::encode(a));
output
}
fn unhexlify(h: &String) -> Result<Vec<u8>, hex::FromHexError> {
let mut prefix = h.to_owned();
let s = prefix.split_off(2);
let result = hex::decode(&s);
result
}