diff --git a/example_solver/Cargo.toml b/example_solver/Cargo.toml index 308bf75..0d3423e 100644 --- a/example_solver/Cargo.toml +++ b/example_solver/Cargo.toml @@ -49,6 +49,3 @@ lib = { git = "https://github.com/ComposableFi/emulated-light-client.git", branc # https://github.com/dalek-cryptography/curve25519-dalek/pull/606 aes-gcm-siv = { git = "https://github.com/RustCrypto/AEADs", rev = "6105d7a5591aefa646a95d12b5e8d3f55a9214ef" } curve25519-dalek = { git = "https://github.com/dalek-cryptography/curve25519-dalek", rev = "8274d5cbb6fc3f38cdc742b4798173895cd2a290" } - - - diff --git a/example_solver/src/chains/mod.rs b/example_solver/src/chains/mod.rs index b2fc4b2..66ed7be 100644 --- a/example_solver/src/chains/mod.rs +++ b/example_solver/src/chains/mod.rs @@ -5,7 +5,7 @@ pub mod solana; use lazy_static::lazy_static; use std::collections::HashMap; -use crate::env; +use crate::{env, SignedPayload}; use ethers::prelude::*; use ethers::signers::LocalWallet; use ethers::utils::hash_message; @@ -144,26 +144,23 @@ pub fn get_token_info(token: &str, blockchain: &str) -> Option<(&'static str, u3 Some((address, info.decimals)) } -pub async fn create_keccak256_signature( - json_data: &mut Value, +pub async fn create_keccak256_signature( + json_data: T, private_key: String, -) -> Result<(), Box> { - let json_str = json_data.to_string(); +) -> Result, Box> { + let json_str = serde_json::to_string(&json_data)?; let json_bytes = json_str.as_bytes(); - let hash = keccak256(json_bytes); - let hash_hex = hex::encode(hash); + let hash = H256(keccak256(json_bytes)); let wallet: LocalWallet = private_key.parse().unwrap(); let eth_message_hash = hash_message(hash); let signature: Signature = wallet.sign_hash(H256::from(eth_message_hash)).unwrap(); - let signature_hex = signature.to_string(); - if let Some(msg) = json_data.get_mut("msg") { - msg["hash"] = Value::String(hash_hex); - msg["signature"] = Value::String(signature_hex); - } - - Ok(()) + Ok(SignedPayload { + payload: json_data, + hash, + signature, + }) } diff --git a/example_solver/src/main.rs b/example_solver/src/main.rs index 7f7ad99..f2cfdb7 100644 --- a/example_solver/src/main.rs +++ b/example_solver/src/main.rs @@ -19,169 +19,228 @@ use serde_json::json; use serde_json::Value; use spl_associated_token_account::get_associated_token_address; use std::env; -use tokio_tungstenite::connect_async; +use ethers::prelude::{Signature, H256}; +use futures::stream::SplitSink; +use serde::{Deserialize, Serialize}; +use tokio::net::TcpStream; +use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream}; +use tokio_tungstenite::tungstenite::Error; use tokio_tungstenite::tungstenite::protocol::Message; +#[derive(Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum ClientMessage { + SolverRegister(SignedPayload), + AuctionBid(SignedPayload), +} + +#[derive(Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum ServerMessage { + SolverRegisterResponse(SolverRegisterResponse), + NewIntent(NewIntentMessage), + AuctionResult(AuctionResultMessage), + ErrorResponse(ErrorResponse), +} + +#[derive(Serialize)] +struct SolverRegisterRequest { + solver_id: String, + solver_addresses: Vec, +} + +#[derive(Deserialize)] +struct SolverRegisterResponse { + message: String, +} + +#[derive(Serialize)] +struct AuctionBidRequest { + intent_id: String, + solver_id: String, + amount: String, +} + +#[derive(Serialize)] +struct SignedPayload { + payload: T, + hash: H256, + signature: Signature, +} + +#[derive(Deserialize)] +struct NewIntentMessage { + intent_id: String, + intent: PostIntentInfo, +} + +#[derive(Deserialize)] +struct AuctionResultMessage { + intent_id: String, + amount: Option, + message: String, +} + +#[derive(Deserialize)] +struct ErrorResponse { + message: String, +} + #[tokio::main] async fn main() { dotenv::dotenv().ok(); - let server_addr = env::var("COMPOSABLE_ENDPOINT").unwrap_or_else(|_| String::from("")); + let server_addr = env::var("COMPOSABLE_ENDPOINT") + .unwrap_or_else(|_| String::from("ws://34.78.217.187:8080")); - let (ws_stream, _) = connect_async(server_addr).await.expect("Failed to connect"); + let (ws_stream, _) = connect_async(format!("{}/ws", server_addr)) + .await + .expect("Failed to connect"); let (mut ws_sender, mut ws_receiver) = ws_stream.split(); - let mut json_data = json!({ - "code": 1, - "msg": { - "solver_id": SOLVER_ID.to_string(), - "solver_addresses": SOLVER_ADDRESSES, - } - }); + let register_request = SolverRegisterRequest { + solver_id: SOLVER_ID.to_string(), + solver_addresses: SOLVER_ADDRESSES.into_iter().map(ToString::to_string).collect(), + }; - create_keccak256_signature(&mut json_data, SOLVER_PRIVATE_KEY.to_string()) + let register_request = create_keccak256_signature(register_request, SOLVER_PRIVATE_KEY.to_string()) .await .unwrap(); - if json_data.get("code").unwrap() == "0" { - println!("{:#?}", json_data); - return; - } + // Serialize the message + let message = ClientMessage::SolverRegister(register_request); + let message_text = serde_json::to_string(&message).unwrap(); ws_sender - .send(Message::Text(json_data.to_string())) + .send(Message::Text(message_text)) .await .expect("Failed to send initial message"); while let Some(msg) = ws_receiver.next().await { - match msg { - Ok(Message::Text(text)) => { - let parsed: Value = serde_json::from_str(&text).unwrap(); - let code = parsed.get("code").unwrap().as_u64().unwrap(); - - println!("{:#?}", parsed); - - if code == 0 { - // error - } else if code == 1 { - // participate auction - let intent_id = parsed - .get("msg") - .unwrap() - .get("intent_id") - .and_then(Value::as_str) - .unwrap(); - let intent_str = parsed - .get("msg") - .unwrap() - .get("intent") - .unwrap() - .to_string(); - let intent_value: Value = serde_json::from_str(&intent_str).unwrap(); - let intent_info: PostIntentInfo = serde_json::from_value(intent_value).unwrap(); - - // calculate best quote + handle_message(&mut ws_sender, msg).await; + } + + println!("Auctioneer went down, please reconnect"); +} + +async fn handle_message(ws_sender: &mut SplitSink>, Message>, msg: Result) -> Result, ()> { + match msg { + Ok(Message::Text(text)) => { + let server_message: ServerMessage = match serde_json::from_str(&text) { + Ok(msg) => msg, + Err(err) => { + eprintln!("Failed to parse server message: {:?}", err); + return Ok(Some(())); + } + }; + + match server_message { + ServerMessage::SolverRegisterResponse(response) => { + println!("Solver registered: {}", response.message); + } + ServerMessage::NewIntent(new_intent) => { + // Participate in auction + let intent_id = new_intent.intent_id; + let intent_info = new_intent.intent; + + // Calculate best quote let final_amount = get_simulate_swap_intent( &intent_info, &intent_info.src_chain, &intent_info.dst_chain, - &String::from("USDT"), + &"USDT".to_string(), ) - .await; - - // decide if participate or not - let mut amount_out_min = U256::zero(); - if let OperationOutput::SwapTransfer(transfer_output) = &intent_info.outputs { - amount_out_min = U256::from_dec_str(&transfer_output.amount_out).unwrap(); - } - - let final_amount = U256::from_dec_str(&final_amount).unwrap(); - - println!("User wants {amount_out_min} token_out, you can provide {final_amount} token_out (after FLAT_FEES + COMISSION)"); - - if final_amount > amount_out_min { - let mut json_data = json!({ - "code": 2, - "msg": { - "intent_id": intent_id, - "solver_id": SOLVER_ID.to_string(), - "amount": final_amount.to_string() - } - }); - - create_keccak256_signature(&mut json_data, SOLVER_PRIVATE_KEY.to_string()) + .await; + + // Decide whether to participate + let amount_out_min = if let OperationOutput::SwapTransfer(transfer_output) = &intent_info.outputs { + U256::from_dec_str(&transfer_output.amount_out).unwrap_or(U256::zero()) + } else { + U256::zero() + }; + + let final_amount_u256 = U256::from_dec_str(&final_amount).unwrap_or(U256::zero()); + + println!( + "User wants {} token_out, you can provide {} token_out (after fees and commission)", + amount_out_min, final_amount_u256 + ); + + if final_amount_u256 > amount_out_min { + // Create AuctionBidRequest + let auction_bid_request = AuctionBidRequest { + intent_id: intent_id.clone(), + solver_id: SOLVER_ID.to_string(), + amount: final_amount.clone(), + }; + + // Create signature + let auction_bid_request = create_keccak256_signature(auction_bid_request, SOLVER_PRIVATE_KEY.to_string()) .await .unwrap(); + // Serialize the message + let message = ClientMessage::AuctionBid(auction_bid_request); + let message_text = serde_json::to_string(&message).unwrap(); + ws_sender - .send(Message::text(json_data.to_string())) + .send(Message::Text(message_text)) .await - .expect("Failed to send message"); + .expect("Failed to send auction bid message"); - let mut intents = INTENTS.write().await; - intents.insert(intent_id.to_string(), intent_info); - drop(intents); + // Store the intent + { + let mut intents = INTENTS.write().await; + intents.insert(intent_id.clone(), intent_info); + } } - } else if code == 3 { - // solver registered - } else if code == 4 { - let intent_id = parsed - .get("msg") - .unwrap() - .get("intent_id") - .and_then(Value::as_str) - .unwrap(); - let amount = parsed - .get("msg") - .unwrap() - .get("amount") - .and_then(Value::as_str); + } + ServerMessage::AuctionResult(auction_result) => { + let intent_id = auction_result.intent_id; + let amount = auction_result.amount; if let Some(amount) = amount { - let msg = parsed - .get("msg") - .unwrap() - .get("msg") - .and_then(Value::as_str) - .unwrap() - .to_string(); - - if msg.contains("won") { - let intent; - { - let intents = INTENTS.read().await; - intent = intents.get(intent_id).unwrap().clone(); - drop(intents); + println!("Auction result for {}: {}", intent_id, auction_result.message); + + // Retrieve the intent + let intent = { + let intents = INTENTS.read().await; + intents.get(&intent_id).cloned() + }; + + if let Some(intent) = intent { + if auction_result.message.contains("won") { + // Handle execution + let handle_result = match intent.dst_chain.as_str() { + "solana" => handle_solana_execution(&intent, &intent_id, &amount).await, + "ethereum" => handle_ethereum_execution(&intent, &intent_id, &amount, intent.src_chain == intent.dst_chain).await, + "mantis" => handle_mantis_execution(&intent, &intent_id, &amount).await, + _ => Err("Unsupported destination chain".to_string()), + }; + + if let Err(err) = handle_result { + eprintln!("Failed to handle execution: {}", err); + } } - if intent.dst_chain == "solana" { - handle_solana_execution(&intent, intent_id, amount) - .await - .unwrap(); - } else if intent.dst_chain == "ethereum" { - handle_ethereum_execution(&intent, intent_id, amount, intent.src_chain == intent.dst_chain) - .await - .unwrap(); - } else if intent.dst_chain == "mantis" { - handle_mantis_execution(&intent, intent_id, amount) - .await - .unwrap(); + // Remove the intent + { + let mut intents = INTENTS.write().await; + intents.remove(&intent_id); } - - // ws_sender.send(Message::text(msg)).await.expect("Failed to send message"); - } - - { - let mut intents = INTENTS.write().await; - intents.remove(&intent_id.to_string()); - drop(intents); + } else { + eprintln!("Intent not found for intent_id: {}", intent_id); } } } + ServerMessage::ErrorResponse(error_response) => { + eprintln!("Error from server: {}", error_response.message); + } } - Ok(Message::Close(_)) | Err(_) => break, - _ => {} + Ok(Some(())) + } + Ok(Message::Close(_)) | Err(_) => Ok(None), + _ => { + Ok(Some(())) } } - - println!("Auctioner went down, please reconnect"); -} +} \ No newline at end of file