diff --git a/Cargo.lock b/Cargo.lock index a23aac9a..3fc993fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3766,6 +3766,7 @@ dependencies = [ [[package]] name = "relayer-utils" version = "0.3.7" +source = "git+https://github.com/zkemail/relayer-utils.git?branch=main#15ec7417a3ce44b03e448ba31f53889e0793c2b3" dependencies = [ "anyhow", "base64 0.21.7", diff --git a/packages/relayer/Cargo.toml b/packages/relayer/Cargo.toml index a9fa0964..66aed43b 100644 --- a/packages/relayer/Cargo.toml +++ b/packages/relayer/Cargo.toml @@ -11,8 +11,7 @@ serde_json = "1.0.128" sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio", "migrate", "uuid", "time", "chrono"] } tokio = { version = "1.40.0", features = ["full"] } tower-http = { version = "0.6.1", features = ["cors"] } -# relayer-utils = { git = "https://github.com/zkemail/relayer-utils.git", branch = "main" } -relayer-utils = { path = "../../../relayer-utils" } +relayer-utils = { git = "https://github.com/zkemail/relayer-utils.git", branch = "main" } slog = { version = "2.7.0", features = [ "max_level_trace", "release_max_level_warn", diff --git a/packages/relayer/config.example.json b/packages/relayer/config.example.json index ce5da39f..d415c77f 100644 --- a/packages/relayer/config.example.json +++ b/packages/relayer/config.example.json @@ -3,6 +3,7 @@ "databaseUrl": "postgres://test@localhost:5432/relayer", "smtpUrl": "http://localhost:3000", "proverUrl": "https://zkemail--email-auth-prover-v1-4-0-flask-app.modal.run", + "alchemyApiKey": "", "paths": { "pem": "./.ic.pem", "emailTemplates": "./email_templates" @@ -16,7 +17,8 @@ "privateKey": "0x...", "rpcUrl": "https://mainnet.infura.io/v3/...", "explorerUrl": "https://etherscan.io/tx/", - "chainId": 1 + "chainId": 1, + "alchemyName": "base-sepolia" } }, "jsonLogger": true diff --git a/packages/relayer/src/chain.rs b/packages/relayer/src/chain.rs index 0555bece..e95224a6 100644 --- a/packages/relayer/src/chain.rs +++ b/packages/relayer/src/chain.rs @@ -9,6 +9,8 @@ use ethers::prelude::*; use ethers::signers::Signer; use ethers::utils::hex; use model::RequestModel; +use relayer_utils::{bytes_to_hex, h160_to_hex, u256_to_hex}; +use slog::error; use statics::SHARED_MUTEX; const CONFIRMATIONS: usize = 1; @@ -120,7 +122,12 @@ impl ChainClient { Ok(is_valid) } - pub async fn call(&self, request: RequestModel, email_auth_msg: EmailAuthMsg) -> Result<()> { + pub async fn call( + &self, + request: RequestModel, + email_auth_msg: EmailAuthMsg, + relayer_state: RelayerState, + ) -> Result<()> { let abi = Abi { functions: vec![request.email_tx_auth.function_abi.clone()] .into_iter() @@ -158,9 +165,74 @@ impl ChainClient { // Now you can use the tokens vector to call the contract function let call = contract.method::<_, ()>(&function.name, custom_tokens)?; + let tx = call.clone().tx; + let from = h160_to_hex(&self.client.address()); + let to = h160_to_hex( + tx.to() + .expect("to not found") + .as_address() + .expect("to not found"), + ); + let data = bytes_to_hex(&tx.data().expect("data not found").to_vec()); + + // Call Alchemy to check for asset changes (Make a POST request to Alchemy) + let alchemy_url = format!( + "https://{}.g.alchemy.com/v2/{}", + relayer_state.config.chains[request.email_tx_auth.chain.as_str()].alchemy_name, + relayer_state.config.alchemy_api_key + ); + + // Prepare the JSON body for the POST request using extracted transaction details + let json_body = serde_json::json!({ + "id": 1, + "jsonrpc": "2.0", + "method": "alchemy_simulateAssetChanges", + "params": [ + { + "from": from, + "to": to, + "value": "0x0", + "data": data, + } + ] + }); + + info!(LOG, "Alchemy request: {:?}", json_body); - let tx = call.send().await?.await?; - info!(LOG, "tx: {:?}", tx.expect("tx not found").transaction_hash); + // Send the POST request + let response = relayer_state + .http_client + .post(&alchemy_url) + .header("accept", "application/json") + .header("content-type", "application/json") + .json(&json_body) + .send() + .await?; + + // Handle the response + if response.status().is_success() { + let response_text = response.text().await?; + info!(LOG, "Alchemy response: {:?}", response_text); + + // Parse the response to check if changes is empty + let response_json: serde_json::Value = serde_json::from_str(&response_text)?; + if let Some(changes) = response_json["result"]["changes"].as_array() { + if !changes.is_empty() { + error!(LOG, "Unexpected changes in Alchemy response: {:?}", changes); + return Err(anyhow!("Unexpected changes in Alchemy response")); + } + } + } else { + let error_text = response.text().await?; + error!(LOG, "Alchemy request failed: {:?}", error_text); + } + + let receipt = call.send().await?.await?; + info!( + LOG, + "tx hash: {:?}", + receipt.expect("tx not found").transaction_hash + ); Ok(()) } diff --git a/packages/relayer/src/config.rs b/packages/relayer/src/config.rs index 05bf998b..c7943d74 100644 --- a/packages/relayer/src/config.rs +++ b/packages/relayer/src/config.rs @@ -12,6 +12,7 @@ pub struct Config { pub database_url: String, pub smtp_url: String, pub prover_url: String, + pub alchemy_api_key: String, pub path: PathConfig, pub icp: IcpConfig, pub chains: HashMap, @@ -39,6 +40,7 @@ pub struct ChainConfig { pub rpc_url: String, pub explorer_url: String, pub chain_id: u32, + pub alchemy_name: String, } // Function to load the configuration from a JSON file diff --git a/packages/relayer/src/mail.rs b/packages/relayer/src/mail.rs index f006658e..c787cb21 100644 --- a/packages/relayer/src/mail.rs +++ b/packages/relayer/src/mail.rs @@ -389,9 +389,11 @@ pub async fn handle_email( ) .await?; - let email_auth_msg = get_email_auth_msg(&email, request.clone(), relayer_state).await?; + let email_auth_msg = get_email_auth_msg(&email, request.clone(), relayer_state.clone()).await?; - chain_client.call(request.clone(), email_auth_msg).await?; + chain_client + .call(request.clone(), email_auth_msg, relayer_state) + .await?; Ok(EmailEvent::Completion { email_addr: parsed_email.get_from_addr()?,