From d2b76d27253d80280e5545112a4368510d4af65d Mon Sep 17 00:00:00 2001 From: jonas089 Date: Fri, 6 Mar 2026 13:05:29 +0100 Subject: [PATCH 01/34] add aws key management to solver and update mvp scripts --- .env.example | 18 +++++ Cargo.lock | 5 ++ mvp.sh | 1 + scripts/start-services.sh | 16 +++++ solver-cli/Cargo.toml | 4 ++ solver-cli/src/commands/account.rs | 65 +++++++++++++++++ solver-cli/src/commands/configure.rs | 27 ++++++- solver-cli/src/commands/fund.rs | 26 ++++++- solver-cli/src/commands/mod.rs | 1 + solver-cli/src/deployment/deployer.rs | 33 ++++++--- solver-cli/src/main.rs | 12 +++- solver-cli/src/rebalancer/config_gen.rs | 23 +++--- solver-cli/src/solver/config_gen.rs | 79 ++++++++++++-------- solver-cli/src/utils/env.rs | 96 +++++++++++++++++++++++++ 14 files changed, 350 insertions(+), 56 deletions(-) create mode 100644 solver-cli/src/commands/account.rs diff --git a/.env.example b/.env.example index 8aad6d5..4cc7924 100644 --- a/.env.example +++ b/.env.example @@ -30,11 +30,29 @@ ANVIL2_CHAIN_ID=31338 # EDEN_PK - Account key for deployments and solving on Eden testnet. EDEN_PK= +# Oracle operator signer — choose one: +# Option A: local key ORACLE_OPERATOR_PK=redacted +# Option B: AWS KMS (comment out ORACLE_OPERATOR_PK above) +#ORACLE_SIGNER_TYPE=aws_kms +#ORACLE_KMS_KEY_ID= +#ORACLE_KMS_REGION=us-east-1 +# Rebalancer signer — choose one: +# Option A: local key (or per-chain REBALANCER__PK) REBALANCER_PRIVATE_KEY=redacted +# Option B: AWS KMS (comment out REBALANCER_PRIVATE_KEY above) +#REBALANCER_SIGNER_TYPE=aws_kms +#REBALANCER_KMS_KEY_ID= +#REBALANCER_KMS_REGION=us-east-1 +# Solver signer — choose one: +# Option A: local key SOLVER_PRIVATE_KEY=redacted +# Option B: AWS KMS (comment out SOLVER_PRIVATE_KEY above) +#SOLVER_SIGNER_TYPE=aws_kms +#SOLVER_KMS_KEY_ID= +#SOLVER_KMS_REGION=us-east-1 # ANVIL1_PK - Deployer for local Anvil chain 1 (Anvil account #0) # The Anvil default key is fine here (local chain only) diff --git a/Cargo.lock b/Cargo.lock index f96a7f8..aea0d58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5270,8 +5270,11 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-signer", + "alloy-signer-aws", "alloy-signer-local", "async-trait", + "aws-config", + "aws-sdk-kms", "hex", "solver-types", "thiserror 2.0.18", @@ -5291,6 +5294,8 @@ dependencies = [ "anyhow", "assert_cmd", "async-trait", + "aws-config", + "aws-sdk-kms", "chrono", "clap", "colored", diff --git a/mvp.sh b/mvp.sh index 9c645b3..c832528 100755 --- a/mvp.sh +++ b/mvp.sh @@ -93,6 +93,7 @@ echo " │ Frontend: http://localhost:5173 │" echo " │ Backend API: http://localhost:3001/api │" echo " │ Aggregator: http://localhost:4000 │" echo " │ Solver: http://localhost:3000 │" +echo " │ Rebalancer: running (see logs/rebalancer.log) │" echo " │ │" echo " │ Docker Stack: │" echo " │ Anvil1: http://localhost:8545 │" diff --git a/scripts/start-services.sh b/scripts/start-services.sh index 9d9c0ff..edf7778 100755 --- a/scripts/start-services.sh +++ b/scripts/start-services.sh @@ -59,3 +59,19 @@ else tail -5 logs/operator.log exit 1 fi + +# ── Rebalancer ──────────────────────────────────────────────────────────────── + +step "Starting Rebalancer..." +make rebalancer > logs/rebalancer.log 2>&1 & +REBALANCER_PID=$! +echo "$REBALANCER_PID" > logs/rebalancer.pid +sleep 3 + +if kill -0 $REBALANCER_PID 2>/dev/null; then + success "Rebalancer running (PID: $REBALANCER_PID)" +else + error "Rebalancer failed to start. Check logs/rebalancer.log" + tail -5 logs/rebalancer.log + exit 1 +fi diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index 75b91e6..c35bd4d 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -21,6 +21,7 @@ solver-runtime = [ "sha3", "futures", "solver-account", + "solver-account?/kms", "solver-core", "solver-delivery", "solver-discovery", @@ -39,6 +40,7 @@ alloy = { version = "1.0", features = [ "full", "provider-http", "signer-local", + "signer-aws", "contract", "sol-types", "json-rpc", @@ -47,6 +49,8 @@ alloy = { version = "1.0", features = [ ] } anyhow = "1.0" async-trait = "0.1" +aws-config = "1" +aws-sdk-kms = "1" # Utilities chrono = { version = "0.4", features = ["serde"] } diff --git a/solver-cli/src/commands/account.rs b/solver-cli/src/commands/account.rs new file mode 100644 index 0000000..faa834d --- /dev/null +++ b/solver-cli/src/commands/account.rs @@ -0,0 +1,65 @@ +use anyhow::{Context, Result}; +use clap::Subcommand; +use std::env; +use std::path::PathBuf; + +use crate::utils::{load_dotenv, SolverSignerConfig}; + +#[derive(Subcommand)] +pub enum AccountCommand { + /// Print the EVM address of the configured solver signing key. + /// + /// Set SOLVER_SIGNER_TYPE in .env to select the backend: + /// - "env" (default): derives address from SOLVER_PRIVATE_KEY + /// - "aws_kms": fetches address from AWS KMS public key + /// (also set SOLVER_KMS_KEY_ID and SOLVER_KMS_REGION) + Address { + /// Project directory (for .env loading) + #[arg(long)] + dir: Option, + }, +} + +impl AccountCommand { + pub async fn run(self) -> Result<()> { + match self { + AccountCommand::Address { dir } => { + let project_dir = dir.unwrap_or_else(|| env::current_dir().unwrap()); + load_dotenv(&project_dir)?; + print_solver_address().await + } + } + } +} + +async fn print_solver_address() -> Result<()> { + match SolverSignerConfig::from_env()? { + SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + use alloy::signers::aws::AwsSigner; + use alloy::signers::Signer; + use aws_sdk_kms::config::Region; + + let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region(Region::new(region)); + if let Some(ep) = endpoint { + loader = loader.endpoint_url(ep); + } + let sdk_config = loader.load().await; + let client = aws_sdk_kms::Client::new(&sdk_config); + let signer = AwsSigner::new(client, key_id, None) + .await + .map_err(|e| anyhow::anyhow!("KMS initialization failed: {e}"))?; + println!("{:?}", Signer::address(&signer)); + } + SolverSignerConfig::Env => { + use crate::chain::ChainClient; + + let raw = env::var("SOLVER_PRIVATE_KEY") + .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; + let pk = if raw.starts_with("0x") { raw } else { format!("0x{raw}") }; + let addr = ChainClient::address_from_pk(&pk)?; + println!("{addr:?}"); + } + } + Ok(()) +} diff --git a/solver-cli/src/commands/configure.rs b/solver-cli/src/commands/configure.rs index eb20698..1db9f8d 100644 --- a/solver-cli/src/commands/configure.rs +++ b/solver-cli/src/commands/configure.rs @@ -48,9 +48,30 @@ impl ConfigureCommand { print_kv("Chains configured", state.chains.len()); - // Derive solver address from solver private key - let solver_pk = env_config.get_solver_pk()?; - let solver_address = ChainClient::address_from_pk(&solver_pk)?; + // Derive solver address based on SOLVER_SIGNER_TYPE (mirrors oracle-operator pattern). + let solver_address = match crate::utils::SolverSignerConfig::from_env()? { + crate::utils::SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + use alloy::signers::aws::AwsSigner; + use alloy::signers::Signer; + use aws_sdk_kms::config::Region; + let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region(Region::new(region)); + if let Some(ep) = endpoint { + loader = loader.endpoint_url(ep); + } + let sdk_config = loader.load().await; + let client = aws_sdk_kms::Client::new(&sdk_config); + let signer = AwsSigner::new(client, key_id, None) + .await + .map_err(|e| anyhow::anyhow!("KMS initialization failed: {e}"))?; + Signer::address(&signer) + } + crate::utils::SolverSignerConfig::Env => { + let solver_pk = env_config.get_solver_pk()?; + ChainClient::address_from_pk(&solver_pk)? + } + }; + print_address("Solver address", &format!("{:?}", solver_address)); // Update solver config in state diff --git a/solver-cli/src/commands/fund.rs b/solver-cli/src/commands/fund.rs index 70aafea..598b0e5 100644 --- a/solver-cli/src/commands/fund.rs +++ b/solver-cli/src/commands/fund.rs @@ -48,9 +48,29 @@ impl FundCommand { let amount: U256 = self.amount.parse()?; - // Get the solver private key - let solver_key = env_config.get_solver_pk()?; - let solver_address = ChainClient::address_from_pk(&solver_key)?; + // Derive solver address from whichever signer is configured (env key or AWS KMS). + let solver_address = match crate::utils::SolverSignerConfig::from_env()? { + crate::utils::SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + use alloy::signers::aws::AwsSigner; + use alloy::signers::Signer; + use aws_sdk_kms::config::Region; + let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region(Region::new(region)); + if let Some(ep) = endpoint { + loader = loader.endpoint_url(ep); + } + let sdk_config = loader.load().await; + let client = aws_sdk_kms::Client::new(&sdk_config); + AwsSigner::new(client, key_id, None) + .await + .map_err(|e| anyhow::anyhow!("KMS initialization failed: {e}"))? + .address() + } + crate::utils::SolverSignerConfig::Env => { + let solver_key = env_config.get_solver_pk()?; + ChainClient::address_from_pk(&solver_key)? + } + }; // Determine which chains to fund let chain_ids: Vec = if let Some(ref chain_arg) = self.chain { diff --git a/solver-cli/src/commands/mod.rs b/solver-cli/src/commands/mod.rs index aded328..d3a425e 100644 --- a/solver-cli/src/commands/mod.rs +++ b/solver-cli/src/commands/mod.rs @@ -1,3 +1,4 @@ +pub mod account; pub mod balances; pub mod chain; pub mod configure; diff --git a/solver-cli/src/deployment/deployer.rs b/solver-cli/src/deployment/deployer.rs index f82ed19..8da9889 100644 --- a/solver-cli/src/deployment/deployer.rs +++ b/solver-cli/src/deployment/deployer.rs @@ -90,14 +90,31 @@ impl Deployer { self.build().await?; } - // Derive operator address from ORACLE_OPERATOR_PK - let operator_pk = std::env::var("ORACLE_OPERATOR_PK") - .context("Missing required environment variable: ORACLE_OPERATOR_PK")?; - let operator_address = format!("{:?}", ChainClient::address_from_pk(&operator_pk)?); - info!( - "Using operator address: {} (derived from ORACLE_OPERATOR_PK)", - operator_address - ); + // Derive operator address from ORACLE_SIGNER_TYPE (env key or AWS KMS). + let operator_address = match crate::utils::OracleSignerConfig::from_env()? { + crate::utils::OracleSignerConfig::AwsKms { key_id, region, endpoint } => { + use alloy::signers::aws::AwsSigner; + use alloy::signers::Signer; + use aws_sdk_kms::config::Region; + let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region(Region::new(region)); + if let Some(ep) = endpoint { + loader = loader.endpoint_url(ep); + } + let sdk_config = loader.load().await; + let client = aws_sdk_kms::Client::new(&sdk_config); + let signer = AwsSigner::new(client, key_id, None) + .await + .map_err(|e| anyhow::anyhow!("Oracle KMS initialization failed: {e}"))?; + format!("{:?}", Signer::address(&signer)) + } + crate::utils::OracleSignerConfig::Env => { + let operator_pk = std::env::var("ORACLE_OPERATOR_PK") + .context("Missing required environment variable: ORACLE_OPERATOR_PK")?; + format!("{:?}", ChainClient::address_from_pk(&operator_pk)?) + } + }; + info!("Using operator address: {}", operator_address); // Try to load Hyperlane deployment artifacts let hyperlane_addresses = Self::load_hyperlane_addresses().ok(); diff --git a/solver-cli/src/main.rs b/solver-cli/src/main.rs index 86b4205..adc0042 100644 --- a/solver-cli/src/main.rs +++ b/solver-cli/src/main.rs @@ -11,9 +11,10 @@ use tracing::Level; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use commands::{ - balances::BalancesCommand, chain::ChainCommand, configure::ConfigureCommand, - deploy::DeployCommand, fund::FundCommand, init::InitCommand, intent::IntentCommand, - order::OrderCommand, rebalancer::RebalancerCommand, solver::SolverCommand, token::TokenCommand, + account::AccountCommand, balances::BalancesCommand, chain::ChainCommand, + configure::ConfigureCommand, deploy::DeployCommand, fund::FundCommand, init::InitCommand, + intent::IntentCommand, order::OrderCommand, rebalancer::RebalancerCommand, + solver::SolverCommand, token::TokenCommand, }; #[derive(Parser)] @@ -79,6 +80,10 @@ enum Commands { /// Rebalancer service management #[command(subcommand)] Rebalancer(RebalancerCommand), + + /// Account utilities (print signing key address) + #[command(subcommand)] + Account(AccountCommand), } fn setup_logging(level: &str) -> anyhow::Result<()> { @@ -114,6 +119,7 @@ async fn main() -> anyhow::Result<()> { Commands::Order(cmd) => cmd.run(cli.output).await, Commands::Balances(cmd) => cmd.run(cli.output).await, Commands::Rebalancer(cmd) => cmd.run(cli.output).await, + Commands::Account(cmd) => cmd.run().await, }; if let Err(e) = result { diff --git a/solver-cli/src/rebalancer/config_gen.rs b/solver-cli/src/rebalancer/config_gen.rs index c8136ed..cf0a2d7 100644 --- a/solver-cli/src/rebalancer/config_gen.rs +++ b/solver-cli/src/rebalancer/config_gen.rs @@ -42,6 +42,13 @@ impl RebalancerConfigGenerator { Err(_) => 69420u64, }; + let signer_inline = match crate::utils::RebalancerSignerConfig::from_env()? { + crate::utils::RebalancerSignerConfig::AwsKms { key_id, region } => { + format!("type = \"aws_kms\"\n key_id = \"{key_id}\"\n region = \"{region}\"") + } + crate::utils::RebalancerSignerConfig::Env => "type = \"env\"".to_string(), + }; + let mut chains_section = String::new(); for chain in &chains { chains_section.push_str(&format!( @@ -53,12 +60,13 @@ domain_id = {chain_id} rpc_url = "{rpc_url}" account = "{account}" [chains.signer] - type = "env" + {signer_inline} "#, name = chain.name, chain_id = chain.chain_id, rpc_url = chain.rpc, account = account, + signer_inline = signer_inline, )); } @@ -243,16 +251,9 @@ fn derive_rebalancer_account(state: &SolverState) -> Result { return normalize_address(address).context("Invalid solver.address in state"); } - let fallback_pk = std::env::var("SOLVER_PRIVATE_KEY") - .or_else(|_| std::env::var("SEPOLIA_PK")) - .map_err(|_| { - anyhow::anyhow!( - "Missing solver address in state and no fallback key found (SOLVER_PRIVATE_KEY / SEPOLIA_PK)" - ) - })?; - let address = ChainClient::address_from_pk(&fallback_pk) - .context("Invalid SOLVER_PRIVATE_KEY/SEPOLIA_PK for rebalancer account derivation")?; - Ok(format!("{:?}", address)) + anyhow::bail!( + "Missing solver address in state. Run 'solver-cli configure' first to populate the solver address." + ) } fn normalize_address(value: &str) -> Result { diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index 36bc0e9..f72b94c 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -15,15 +15,32 @@ impl ConfigGenerator { anyhow::bail!("No chains configured"); } - // Read private key from environment at generation time - let solver_private_key = std::env::var("SOLVER_PRIVATE_KEY") - .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; - - // Ensure the key has 0x prefix - let solver_private_key = if solver_private_key.starts_with("0x") { - solver_private_key - } else { - format!("0x{}", solver_private_key) + // Build account section based on SOLVER_SIGNER_TYPE. + let account_section = match crate::utils::SolverSignerConfig::from_env()? { + crate::utils::SolverSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { + let endpoint_line = endpoint + .map(|ep| format!("endpoint = \"{ep}\"\n")) + .unwrap_or_default(); + format!( + "[account]\nprimary = \"kms\"\n\n[account.implementations.kms]\nkey_id = \"{key_id}\"\nregion = \"{region}\"\n{endpoint_line}" + ) + } + crate::utils::SolverSignerConfig::Env => { + let raw = std::env::var("SOLVER_PRIVATE_KEY") + .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; + let key = if raw.starts_with("0x") { + raw + } else { + format!("0x{raw}") + }; + format!( + "[account]\nprimary = \"local\"\n\n[account.implementations.local]\nprivate_key = \"{key}\"" + ) + } }; // Collect all chain IDs @@ -148,11 +165,7 @@ cleanup_interval_seconds = 60 # ============================================================================ # ACCOUNT # ============================================================================ -[account] -primary = "local" - -[account.implementations.local] -private_key = "{solver_private_key}" +{account_section} # ============================================================================ # NETWORKS @@ -231,7 +244,7 @@ output = {{ {output_oracles} }} chain_ids.len(), chain_ids_str, solver_id = state.solver.solver_id.as_deref().unwrap_or("solver-001"), - solver_private_key = solver_private_key, + account_section = account_section, networks_section = networks_section.trim(), chain_ids_str = chain_ids_str, input_oracles = input_oracles.join(", "), @@ -304,6 +317,15 @@ input_settler_address = "{}" )); } + let signer_section = match crate::utils::OracleSignerConfig::from_env()? { + crate::utils::OracleSignerConfig::AwsKms { key_id, region, .. } => { + format!( + "[signer]\ntype = \"aws_kms\"\nkey_id = \"{key_id}\"\nregion = \"{region}\"" + ) + } + crate::utils::OracleSignerConfig::Env => "[signer]\ntype = \"env\"".to_string(), + }; + let config = format!( r#"# Auto-generated oracle operator configuration # DO NOT EDIT MANUALLY - regenerate with 'solver-cli configure' @@ -313,8 +335,7 @@ input_settler_address = "{}" operator_address = "{operator_address}" # Operator signer configuration (single signer used across all chains) -[signer] -type = "env" +{signer_section} # Polling interval in seconds poll_interval_seconds = 3 @@ -323,6 +344,7 @@ poll_interval_seconds = 3 {chains_section}"#, state.chains.len(), operator_address = operator_address, + signer_section = signer_section, chains_section = chains_section.trim(), ); @@ -496,13 +518,17 @@ poll_interval_seconds = 3 anyhow::bail!("No chains configured"); } - // Read signer keys from environment - let evm_signer_key = std::env::var("SOLVER_PRIVATE_KEY") - .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; - let evm_signer_key = if evm_signer_key.starts_with("0x") { - evm_signer_key - } else { - format!("0x{}", evm_signer_key) + // Build EVM signer config based on SOLVER_SIGNER_TYPE. + let evm_signer = match crate::utils::SolverSignerConfig::from_env()? { + crate::utils::SolverSignerConfig::AwsKms { key_id, region, .. } => { + serde_json::json!({ "type": "aws", "id": key_id, "region": region }) + } + crate::utils::SolverSignerConfig::Env => { + let raw = std::env::var("SOLVER_PRIVATE_KEY") + .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; + let key = if raw.starts_with("0x") { raw } else { format!("0x{raw}") }; + serde_json::json!({ "type": "hexKey", "key": key }) + } }; let cosmos_signer_key = std::env::var("CELESTIA_SIGNER_KEY").unwrap_or_else(|_| { @@ -558,10 +584,7 @@ poll_interval_seconds = 3 }, "protocol": "ethereum", "rpcUrls": [{ "http": chain.rpc }], - "signer": { - "type": "hexKey", - "key": evm_signer_key - }, + "signer": evm_signer.clone(), "mailbox": mailbox, "merkleTreeHook": merkle_tree_hook, "validatorAnnounce": validator_announce, diff --git a/solver-cli/src/utils/env.rs b/solver-cli/src/utils/env.rs index 9f82d2b..570d862 100644 --- a/solver-cli/src/utils/env.rs +++ b/solver-cli/src/utils/env.rs @@ -123,3 +123,99 @@ impl EnvConfig { .context("Missing required environment variable: SOLVER_PRIVATE_KEY") } } + +/// Solver signer configuration — mirrors the oracle-operator's SignerConfig. +/// +/// Select the backend via SOLVER_SIGNER_TYPE in .env: +/// - "env" (default): reads SOLVER_PRIVATE_KEY +/// - "aws_kms": reads SOLVER_KMS_KEY_ID + SOLVER_KMS_REGION (+ optional SOLVER_KMS_ENDPOINT) +#[derive(Debug, Clone)] +pub enum SolverSignerConfig { + /// Local private key read from SOLVER_PRIVATE_KEY env var (default) + Env, + /// AWS KMS signing — private key never leaves the HSM + AwsKms { + key_id: String, + region: String, + /// Optional custom endpoint, e.g. for LocalStack (SOLVER_KMS_ENDPOINT) + endpoint: Option, + }, +} + +impl SolverSignerConfig { + /// Load from SOLVER_SIGNER_TYPE (defaults to "env" if unset). + pub fn from_env() -> Result { + let signer_type = env::var("SOLVER_SIGNER_TYPE").unwrap_or_else(|_| "env".to_string()); + match signer_type.as_str() { + "aws_kms" => { + let key_id = env::var("SOLVER_KMS_KEY_ID") + .context("Missing required environment variable: SOLVER_KMS_KEY_ID")?; + let region = env::var("SOLVER_KMS_REGION") + .context("Missing required environment variable: SOLVER_KMS_REGION")?; + let endpoint = env::var("SOLVER_KMS_ENDPOINT").ok(); + Ok(Self::AwsKms { key_id, region, endpoint }) + } + _ => Ok(Self::Env), + } + } +} + +/// Rebalancer signer configuration. +/// +/// Select the backend via REBALANCER_SIGNER_TYPE in .env: +/// - "env" (default): reads REBALANCER_PRIVATE_KEY (or per-chain REBALANCER__PK) +/// - "aws_kms": reads REBALANCER_KMS_KEY_ID + REBALANCER_KMS_REGION +#[derive(Debug, Clone)] +pub enum RebalancerSignerConfig { + Env, + AwsKms { key_id: String, region: String }, +} + +impl RebalancerSignerConfig { + pub fn from_env() -> Result { + let signer_type = + env::var("REBALANCER_SIGNER_TYPE").unwrap_or_else(|_| "env".to_string()); + match signer_type.as_str() { + "aws_kms" => { + let key_id = env::var("REBALANCER_KMS_KEY_ID") + .context("Missing required environment variable: REBALANCER_KMS_KEY_ID")?; + let region = env::var("REBALANCER_KMS_REGION") + .context("Missing required environment variable: REBALANCER_KMS_REGION")?; + Ok(Self::AwsKms { key_id, region }) + } + _ => Ok(Self::Env), + } + } +} + +/// Oracle operator signer configuration. +/// +/// Select the backend via ORACLE_SIGNER_TYPE in .env: +/// - "env" (default): reads ORACLE_OPERATOR_PK +/// - "aws_kms": reads ORACLE_KMS_KEY_ID + ORACLE_KMS_REGION (+ optional ORACLE_KMS_ENDPOINT) +#[derive(Debug, Clone)] +pub enum OracleSignerConfig { + Env, + AwsKms { + key_id: String, + region: String, + endpoint: Option, + }, +} + +impl OracleSignerConfig { + pub fn from_env() -> Result { + let signer_type = env::var("ORACLE_SIGNER_TYPE").unwrap_or_else(|_| "env".to_string()); + match signer_type.as_str() { + "aws_kms" => { + let key_id = env::var("ORACLE_KMS_KEY_ID") + .context("Missing required environment variable: ORACLE_KMS_KEY_ID")?; + let region = env::var("ORACLE_KMS_REGION") + .context("Missing required environment variable: ORACLE_KMS_REGION")?; + let endpoint = env::var("ORACLE_KMS_ENDPOINT").ok(); + Ok(Self::AwsKms { key_id, region, endpoint }) + } + _ => Ok(Self::Env), + } + } +} From 509afd20b8465b4efe9255cd4b1721dbfc45f607 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Fri, 6 Mar 2026 13:23:57 +0100 Subject: [PATCH 02/34] fix config --- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 +++++++++++++++++++ hyperlane/hyperlane-cosmosnative.json | 8 +++++ .../registry/chains/anvil1/addresses.yaml | 14 +++++++++ .../registry/chains/anvil2/addresses.yaml | 14 +++++++++ .../warp_routes/USDC/warp-config-config.yaml | 19 ++++++++++++ solver-cli/src/rebalancer/config_gen.rs | 1 - solver-cli/src/solver/config_gen.rs | 6 ++-- 8 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 hyperlane/hyperlane-addresses.json create mode 100644 hyperlane/hyperlane-cosmosnative.json create mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml create mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json new file mode 100644 index 0000000..64f24e3 --- /dev/null +++ b/hyperlane/hyperlane-addresses.json @@ -0,0 +1,29 @@ +{ + "anvil1": { + "chain_id": 31337, + "domain_id": 131337, + "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "warp_token_type": "collateral", + "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "anvil2": { + "chain_id": 31338, + "domain_id": 31338, + "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "warp_token_type": "synthetic", + "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + }, + "celestiadev": { + "domain_id": 69420, + "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" + } +} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json new file mode 100644 index 0000000..ae2a37b --- /dev/null +++ b/hyperlane/hyperlane-cosmosnative.json @@ -0,0 +1,8 @@ +{ + "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", + "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", + "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" +} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml new file mode 100644 index 0000000..86b298a --- /dev/null +++ b/hyperlane/registry/chains/anvil1/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" +merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" +validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml new file mode 100644 index 0000000..ad2a018 --- /dev/null +++ b/hyperlane/registry/chains/anvil2/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" +proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml new file mode 100644 index 0000000..4ad5a1b --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../schema.json +tokens: + - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + chainName: anvil1 + collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + connections: + - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 + decimals: 6 + name: USDC + standard: EvmHypCollateral + symbol: USDC + - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + chainName: anvil2 + connections: + - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 + decimals: 6 + name: USDC + standard: EvmHypSynthetic + symbol: USDC diff --git a/solver-cli/src/rebalancer/config_gen.rs b/solver-cli/src/rebalancer/config_gen.rs index cf0a2d7..16b7832 100644 --- a/solver-cli/src/rebalancer/config_gen.rs +++ b/solver-cli/src/rebalancer/config_gen.rs @@ -4,7 +4,6 @@ use std::collections::BTreeMap; use std::path::Path; use tokio::fs; -use crate::chain::ChainClient; use crate::state::SolverState; /// Generates rebalancer configuration files diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index f72b94c..de32146 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -334,12 +334,12 @@ input_settler_address = "{}" # Operator address (must match CentralizedOracle operator) operator_address = "{operator_address}" -# Operator signer configuration (single signer used across all chains) -{signer_section} - # Polling interval in seconds poll_interval_seconds = 3 +# Operator signer configuration (single signer used across all chains) +{signer_section} + # Chain configurations {chains_section}"#, state.chains.len(), From 5ce102be9899fb5d03fcec0456c4d10440c7a55f Mon Sep 17 00:00:00 2001 From: jonas089 Date: Fri, 6 Mar 2026 13:53:02 +0100 Subject: [PATCH 03/34] wip --- .env.example | 3 ++ frontend/server.js | 7 +++-- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 ------------------ hyperlane/hyperlane-cosmosnative.json | 8 ----- .../registry/chains/anvil1/addresses.yaml | 14 --------- .../registry/chains/anvil2/addresses.yaml | 14 --------- .../warp_routes/USDC/warp-config-config.yaml | 19 ------------ rebalancer/src/client.rs | 28 +++++++++++++++++ rebalancer/src/service.rs | 30 +++++++++++++++++++ solver-cli/src/commands/chain.rs | 28 +++++++++++++++-- solver-cli/src/rebalancer/config_gen.rs | 30 ++++++++++++++----- 12 files changed, 116 insertions(+), 96 deletions(-) delete mode 100644 hyperlane/hyperlane-addresses.json delete mode 100644 hyperlane/hyperlane-cosmosnative.json delete mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml delete mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml diff --git a/.env.example b/.env.example index 4cc7924..38f62cc 100644 --- a/.env.example +++ b/.env.example @@ -38,6 +38,9 @@ ORACLE_OPERATOR_PK=redacted #ORACLE_KMS_KEY_ID= #ORACLE_KMS_REGION=us-east-1 +# Bridge signer — hot wallet key for the frontend bridge tool (Node.js cannot use KMS directly) +BRIDGE_SIGNER_PK=redacted + # Rebalancer signer — choose one: # Option A: local key (or per-chain REBALANCER__PK) REBALANCER_PRIVATE_KEY=redacted diff --git a/frontend/server.js b/frontend/server.js index 920cebe..9f7f3fe 100644 --- a/frontend/server.js +++ b/frontend/server.js @@ -442,8 +442,11 @@ app.post('/api/rebalance', async (req, res) => { const dstBalanceToken = dst.warpType === 'collateral' ? dst.underlying : dst.warpToken; // 2. Setup viem clients - const solverPk = process.env.SOLVER_PRIVATE_KEY; - if (!solverPk) throw new Error('SOLVER_PRIVATE_KEY not set in .env'); + // BRIDGE_SIGNER_PK is a hot wallet key for the frontend bridge tool. + // Falls back to REBALANCER_PRIVATE_KEY, then SOLVER_PRIVATE_KEY. + // Required when solver/rebalancer use AWS KMS (which Node.js cannot sign with directly). + const solverPk = process.env.BRIDGE_SIGNER_PK || process.env.REBALANCER_PRIVATE_KEY || process.env.SOLVER_PRIVATE_KEY; + if (!solverPk) throw new Error('No bridge signer key found. Set BRIDGE_SIGNER_PK (or REBALANCER_PRIVATE_KEY / SOLVER_PRIVATE_KEY) in .env'); const solver = privateKeyToAccount(`0x${solverPk.replace('0x', '')}`); const srcChain = makeViemChain(src.chainId, from, src.rpc); diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json deleted file mode 100644 index 64f24e3..0000000 --- a/hyperlane/hyperlane-addresses.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "anvil1": { - "chain_id": 31337, - "domain_id": 131337, - "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", - "warp_token_type": "collateral", - "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" - }, - "anvil2": { - "chain_id": 31338, - "domain_id": 31338, - "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "warp_token_type": "synthetic", - "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" - }, - "celestiadev": { - "domain_id": 69420, - "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", - "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" - } -} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json deleted file mode 100644 index ae2a37b..0000000 --- a/hyperlane/hyperlane-cosmosnative.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", - "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", - "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" -} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml deleted file mode 100644 index 86b298a..0000000 --- a/hyperlane/registry/chains/anvil1/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" -merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" -testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" -validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml deleted file mode 100644 index ad2a018..0000000 --- a/hyperlane/registry/chains/anvil2/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" -proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" -staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml deleted file mode 100644 index 4ad5a1b..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# yaml-language-server: $schema=../schema.json -tokens: - - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" - chainName: anvil1 - collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - connections: - - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 - decimals: 6 - name: USDC - standard: EvmHypCollateral - symbol: USDC - - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" - chainName: anvil2 - connections: - - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 - decimals: 6 - name: USDC - standard: EvmHypSynthetic - symbol: USDC diff --git a/rebalancer/src/client.rs b/rebalancer/src/client.rs index b371efe..74e8f54 100644 --- a/rebalancer/src/client.rs +++ b/rebalancer/src/client.rs @@ -41,6 +41,7 @@ sol! { #[sol(rpc)] interface IERC20 { function balanceOf(address account) external view returns (uint256); + function approve(address spender, uint256 amount) external returns (bool); } #[sol(rpc)] @@ -151,6 +152,33 @@ impl ChainClient { .context("Failed to query pending account nonce") } + pub async fn approve_erc20( + &self, + token: Address, + spender: Address, + amount: U256, + ) -> Result { + let call = IERC20::approveCall { spender, amount }; + let call_data = Bytes::from(call.abi_encode()); + + let tx = TransactionRequest::default() + .to(token) + .input(call_data.into()); + + let pending = self + .provider + .send_transaction(tx) + .await + .with_context(|| { + format!( + "Failed to send ERC20 approve tx: token={} spender={} amount={}", + token, spender, amount + ) + })?; + + Ok(*pending.tx_hash()) + } + pub async fn quote_transfer_remote( &self, source_router: Address, diff --git a/rebalancer/src/service.rs b/rebalancer/src/service.rs index e9b8f79..b158bfd 100644 --- a/rebalancer/src/service.rs +++ b/rebalancer/src/service.rs @@ -499,6 +499,36 @@ impl RebalancerService { ); } + if source_token_config.asset_type == AssetType::Erc20 { + if let Some(erc20_address) = source_token_config.address { + match source_client + .approve_erc20(erc20_address, source_collateral_token, transfer_amount) + .await + { + Ok(tx_hash) => { + info!( + "Asset {} ERC20 approve submitted: route {} -> {} token={} spender={} amount={} {} tx_hash={}", + asset.symbol, + source_chain.name, + destination_chain.name, + erc20_address, + source_collateral_token, + format_raw_u128(transfer.amount_raw, asset.decimals), + asset.symbol, + tx_hash, + ); + } + Err(err) => { + warn!( + "Asset {} route {} -> {} ERC20 approve failed; skipping transfer:\n{:#}", + asset.symbol, source_chain.name, destination_chain.name, err + ); + continue; + } + } + } + } + match source_client .submit_transfer_remote( source_collateral_token, diff --git a/solver-cli/src/commands/chain.rs b/solver-cli/src/commands/chain.rs index f71c3c4..4940f40 100644 --- a/solver-cli/src/commands/chain.rs +++ b/solver-cli/src/commands/chain.rs @@ -45,6 +45,12 @@ pub enum ChainCommand { #[arg(long, default_value = "6")] decimals: u8, + /// Hyperlane warp token router address (HypERC20Collateral or HypERC20Synthetic). + /// Required for HypCollateral chains where the warp router differs from the ERC20. + /// Optional for HypSynthetic chains (the token address is used as fallback). + #[arg(long)] + warp_token: Option, + /// Project directory #[arg(long)] dir: Option, @@ -118,6 +124,7 @@ struct ChainAddParams { oracle: String, tokens: Vec, default_decimals: u8, + warp_token: Option, dir: Option, } @@ -133,6 +140,7 @@ impl ChainCommand { oracle, token, decimals, + warp_token, dir, } => { Self::add( @@ -145,6 +153,7 @@ impl ChainCommand { oracle, tokens: token, default_decimals: decimals, + warp_token, dir, }, output, @@ -166,6 +175,7 @@ impl ChainCommand { oracle, tokens, default_decimals, + warp_token, dir, } = params; let out = OutputFormatter::new(output); @@ -200,17 +210,31 @@ impl ChainCommand { } // Build contracts struct + let hyperlane = warp_token.as_ref().map(|addr| { + crate::state::HyperlaneAddresses { + mailbox: None, + merkle_tree_hook: None, + validator_announce: None, + igp: None, + warp_token: Some(addr.clone()), + warp_token_type: Some("collateral".to_string()), + } + }); + let contracts = ContractAddresses { input_settler_escrow: Some(input_settler.clone()), output_settler_simple: Some(output_settler.clone()), oracle: Some(oracle.clone()), - permit2: None, // TODO: Add permit2 parameter to chain add command - hyperlane: None, + permit2: None, + hyperlane, }; print_address("InputSettlerEscrow", &input_settler); print_address("OutputSettlerSimple", &output_settler); print_address("CentralizedOracle", &oracle); + if let Some(ref addr) = warp_token { + print_address("Warp token router", addr); + } // Build tokens map let mut token_map: HashMap = HashMap::new(); diff --git a/solver-cli/src/rebalancer/config_gen.rs b/solver-cli/src/rebalancer/config_gen.rs index 16b7832..810da53 100644 --- a/solver-cli/src/rebalancer/config_gen.rs +++ b/solver-cli/src/rebalancer/config_gen.rs @@ -88,10 +88,11 @@ decimals = {decimals} chain_id = {chain_id} type = "erc20" address = "{address}" - collateral_token = "{address}" + collateral_token = "{collateral_token}" "#, chain_id = token.chain_id, - address = token.address + address = token.address, + collateral_token = token.collateral_token, )); } @@ -156,6 +157,9 @@ max_transfer_bps = 5000 struct RebalancerTokenEntry { chain_id: u64, address: String, + /// Hyperlane warp token router address for `transferRemote` / `quoteTransferRemote`. + /// Falls back to `address` if no warp token is deployed. + collateral_token: String, } #[derive(Debug, Clone)] @@ -168,15 +172,23 @@ struct RebalancerAsset { } fn collect_assets(state: &SolverState) -> Result> { - let mut by_symbol: BTreeMap> = BTreeMap::new(); + let mut by_symbol: BTreeMap> = BTreeMap::new(); for (chain_id, chain) in &state.chains { + let warp_token = chain + .contracts + .hyperlane + .as_ref() + .and_then(|h| h.warp_token.clone()); for token in chain.tokens.values() { let normalized = token.symbol.to_ascii_uppercase(); + // Use warp token address as collateral_token if available; otherwise fall back to ERC20 + let collateral = warp_token.clone().unwrap_or_else(|| token.address.clone()); by_symbol.entry(normalized).or_default().push(( *chain_id, token.address.clone(), token.decimals, + collateral, )); } } @@ -187,11 +199,11 @@ fn collect_assets(state: &SolverState) -> Result> { continue; } - entries.sort_by_key(|(chain_id, _, _)| *chain_id); + entries.sort_by_key(|(chain_id, _, _, _)| *chain_id); let expected_decimals = entries[0].2; if entries .iter() - .any(|(_, _, decimals)| *decimals != expected_decimals) + .any(|(_, _, decimals, _)| *decimals != expected_decimals) { anyhow::bail!( "Token {} has inconsistent decimals across chains, cannot generate rebalancer config", @@ -199,7 +211,7 @@ fn collect_assets(state: &SolverState) -> Result> { ); } - let chain_ids: Vec = entries.iter().map(|(chain_id, _, _)| *chain_id).collect(); + let chain_ids: Vec = entries.iter().map(|(chain_id, _, _, _)| *chain_id).collect(); let weights = equal_weight_distribution(&chain_ids, 1_000_000); let min_weights: Vec<(u64, f64)> = weights .iter() @@ -216,7 +228,11 @@ fn collect_assets(state: &SolverState) -> Result> { decimals: expected_decimals, tokens: entries .into_iter() - .map(|(chain_id, address, _)| RebalancerTokenEntry { chain_id, address }) + .map(|(chain_id, address, _, collateral_token)| RebalancerTokenEntry { + chain_id, + address, + collateral_token, + }) .collect(), weights, min_weights, From 8fcb828d30e5c31127830ef4db148d49b3edc73a Mon Sep 17 00:00:00 2001 From: jonas089 Date: Fri, 6 Mar 2026 14:01:25 +0100 Subject: [PATCH 04/34] successful rebalancing + add testnet doc --- docs/deploy-testnet.md | 265 ++++++++++++++++++ hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 ++ hyperlane/hyperlane-cosmosnative.json | 8 + .../registry/chains/anvil1/addresses.yaml | 14 + .../registry/chains/anvil2/addresses.yaml | 14 + .../warp_routes/USDC/warp-config-config.yaml | 19 ++ 7 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 docs/deploy-testnet.md create mode 100644 hyperlane/hyperlane-addresses.json create mode 100644 hyperlane/hyperlane-cosmosnative.json create mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml create mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml diff --git a/docs/deploy-testnet.md b/docs/deploy-testnet.md new file mode 100644 index 0000000..7a0caf6 --- /dev/null +++ b/docs/deploy-testnet.md @@ -0,0 +1,265 @@ +# Connect to Existing Testnet Deployments + +This guide covers connecting solver-cli to chains where OIF contracts, Hyperlane warp routes, and the Celestia forwarding layer are **already deployed and running**. No contract deployment is needed — just register your existing addresses and start the services. + +**Assumed already in place:** +- OIF contracts (InputSettlerEscrow, OutputSettlerSimple, CentralizedOracle) deployed on each EVM chain +- Hyperlane warp routes deployed and enrolled between EVM chains and Celestia +- Celestia synthetic tokens deployed and enrolled +- Hyperlane relayer running and connected to all chains +- Forwarding relayer running and accessible at a known URL + +--- + +## Step 1: Configure .env + +Copy `.env.example` to `.env` and fill in the values for your setup. + +### Signing keys + +Choose one approach per service. All three services (solver, oracle, rebalancer) can use different keys independently. + +**Option A — Local private keys:** +```bash +SOLVER_PRIVATE_KEY=0x +ORACLE_OPERATOR_PK=0x +REBALANCER_PRIVATE_KEY=0x +``` + +**Option B — AWS KMS (recommended for production):** +```bash +# Solver +SOLVER_SIGNER_TYPE=aws_kms +SOLVER_KMS_KEY_ID= +SOLVER_KMS_REGION=us-east-1 + +# Oracle operator +ORACLE_SIGNER_TYPE=aws_kms +ORACLE_KMS_KEY_ID= +ORACLE_KMS_REGION=us-east-1 + +# Rebalancer +REBALANCER_SIGNER_TYPE=aws_kms +REBALANCER_KMS_KEY_ID= +REBALANCER_KMS_REGION=us-east-1 +``` + +All three can share the same KMS key or use separate keys — just set the same UUID for each. + +> KMS keys must be asymmetric secp256k1 signing keys. See [aws-kms-key-import.md](aws-kms-key-import.md) for setup instructions. + +**Frontend bridge signer (always a local key — Node.js cannot use KMS):** +```bash +BRIDGE_SIGNER_PK=0x +``` + +### Celestia / forwarding + +```bash +CELESTIA_DOMAIN= +FORWARDING_BACKEND=http://: +``` + +### Aggregator integrity secret + +```bash +INTEGRITY_SECRET= +``` + +--- + +## Step 2: Initialize state + +```bash +solver-cli init +``` + +This creates `.config/state.json` to track chain registrations and generated config paths. + +--- + +## Step 3: Register chains + +Register each EVM chain with its deployed contract addresses. + +### HypCollateral chains + +On chains where the warp token is a **HypERC20Collateral** (wraps an existing ERC20), pass both the underlying ERC20 address via `--token` and the collateral router via `--warp-token`: + +```bash +solver-cli chain add \ + --name chain-a \ + --rpc https://rpc.chain-a.example \ + --chain-id 12345 \ + --input-settler 0x \ + --output-settler 0x \ + --oracle 0x \ + --token USDC=0x:6 \ + --token USDT=0x:6 \ + --warp-token 0x +``` + +> `--warp-token` is the address the rebalancer calls `transferRemote` on. It must be the collateral router, not the underlying ERC20. The rebalancer will automatically `approve` the router to spend the ERC20 before each transfer. + +> If different tokens have different collateral routers, register them separately using `solver-cli token add` after chain registration (see below). + +### HypSynthetic chains + +On chains where the warp token is a **HypERC20Synthetic** (mints/burns bridged tokens), the synthetic contract IS the token — pass it as the `--token` address and omit `--warp-token`: + +```bash +solver-cli chain add \ + --name chain-b \ + --rpc https://rpc.chain-b.example \ + --chain-id 67890 \ + --input-settler 0x \ + --output-settler 0x \ + --oracle 0x \ + --token USDC=0x:6 \ + --token USDT=0x:6 +``` + +### Adding more tokens to an already-registered chain + +```bash +solver-cli token add --chain chain-a --symbol DAI --address 0x --decimals 18 +``` + +### Verify registrations + +```bash +solver-cli chain list +solver-cli token list +``` + +--- + +## Step 4: Generate configs + +```bash +solver-cli configure +``` + +This reads your registered chains, derives the solver address from your signing key (local or KMS), and writes: + +| File | Purpose | +|---|---| +| `.config/solver.toml` | Solver engine config with all-to-all routes | +| `.config/oracle.toml` | Oracle operator config for all chains | +| `.config/rebalancer.toml` | Rebalancer config with equal-weight distribution | +| `.config/hyperlane-relayer.json` | Hyperlane relayer signer config | +| `oif/oif-aggregator/config/config.json` | Aggregator config | + +Forwarding section in `rebalancer.toml` is populated from `CELESTIA_DOMAIN` and `FORWARDING_BACKEND`. + +--- + +## Step 5: Verify solver and oracle addresses + +Confirm the addresses that will be used on-chain before funding them. + +```bash +solver-cli account address +``` + +For the oracle operator address, check the generated config: + +```bash +grep operator_address .config/oracle.toml +``` + +--- + +## Step 6: Fund accounts + +The solver needs gas (native token) on every chain where it will fill orders. The oracle operator needs gas on every chain where it will submit attestations (i.e. every origin chain). The rebalancer needs gas on every source chain it will initiate transfers from. + +```bash +# Example using cast — repeat for each chain +cast send \ + --rpc-url https://rpc.chain-a.example \ + --private-key \ + --value 0.1ether + +cast send \ + --rpc-url https://rpc.chain-a.example \ + --private-key \ + --value 0.05ether +``` + +The solver also needs token inventory on destination chains to fill orders. Fund it with the relevant tokens on each chain. + +--- + +## Step 7: Start services + +Each service reads from the generated configs in `.config/`. Start them in separate terminals or as background processes: + +```bash +# Aggregator (quote aggregation API on port 4000) +make aggregator + +# Solver (fills orders, claims on oracle confirmation) +make solver + +# Oracle operator (signs and submits fill attestations) +make operator + +# Rebalancer (maintains token distribution across chains) +make rebalancer + +# Frontend (bridge UI + API on ports 3001 / 5173) +./scripts/start-frontend.sh +``` + +Or use the convenience script that starts all backend services together: + +```bash +./scripts/start-services.sh +``` + +--- + +## Rebalancer behavior + +The rebalancer polls each chain's solver token balance every 30 seconds and transfers from over-weight chains to under-weight chains when any chain falls below its `min_weight` threshold. + +By default, `solver-cli configure` sets equal weights across all chains (50/50 for two chains, 33/33/33 for three, etc.) with a ±20% tolerance before triggering a rebalance. + +Transfers route through Celestia: the rebalancer calls `transferRemote` on the source chain's warp router toward the Celestia forwarding domain, and the forwarding relayer delivers the tokens to the destination chain. + +**For HypCollateral chains**, the rebalancer sends an `approve` transaction to the underlying ERC20 before each `transferRemote`. This is handled automatically — no manual approval is needed. + +To run a single rebalance cycle manually: + +```bash +solver-cli rebalancer start --once +``` + +--- + +## Updating a registered chain + +If you need to update contract addresses or add a warp token to an existing chain registration, re-run `chain add` with the same `--name` — it will overwrite the existing entry: + +```bash +solver-cli chain add --name chain-a --rpc ... --input-settler ... --output-settler ... --oracle ... --warp-token 0x +``` + +Then regenerate configs: + +```bash +solver-cli configure +``` + +And restart the affected services. + +--- + +## Removing a chain + +```bash +solver-cli chain remove --chain chain-b +solver-cli configure +# Restart services +``` diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json new file mode 100644 index 0000000..64f24e3 --- /dev/null +++ b/hyperlane/hyperlane-addresses.json @@ -0,0 +1,29 @@ +{ + "anvil1": { + "chain_id": 31337, + "domain_id": 131337, + "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "warp_token_type": "collateral", + "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "anvil2": { + "chain_id": 31338, + "domain_id": 31338, + "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "warp_token_type": "synthetic", + "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + }, + "celestiadev": { + "domain_id": 69420, + "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" + } +} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json new file mode 100644 index 0000000..ae2a37b --- /dev/null +++ b/hyperlane/hyperlane-cosmosnative.json @@ -0,0 +1,8 @@ +{ + "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", + "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", + "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" +} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml new file mode 100644 index 0000000..86b298a --- /dev/null +++ b/hyperlane/registry/chains/anvil1/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" +merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" +validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml new file mode 100644 index 0000000..ad2a018 --- /dev/null +++ b/hyperlane/registry/chains/anvil2/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" +proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml new file mode 100644 index 0000000..4ad5a1b --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../schema.json +tokens: + - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + chainName: anvil1 + collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + connections: + - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 + decimals: 6 + name: USDC + standard: EvmHypCollateral + symbol: USDC + - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + chainName: anvil2 + connections: + - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 + decimals: 6 + name: USDC + standard: EvmHypSynthetic + symbol: USDC From 83c4600b70eb5b81962c9283ef4fb4a7a08a244b Mon Sep 17 00:00:00 2001 From: jonas089 Date: Fri, 6 Mar 2026 15:16:34 +0100 Subject: [PATCH 05/34] account for nonce in rebalancer --- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 ------------------- hyperlane/hyperlane-cosmosnative.json | 8 ----- .../registry/chains/anvil1/addresses.yaml | 14 --------- .../registry/chains/anvil2/addresses.yaml | 14 --------- .../warp_routes/USDC/warp-config-config.yaml | 19 ------------ rebalancer/src/client.rs | 20 +++++++++++-- 7 files changed, 18 insertions(+), 88 deletions(-) delete mode 100644 hyperlane/hyperlane-addresses.json delete mode 100644 hyperlane/hyperlane-cosmosnative.json delete mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml delete mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json deleted file mode 100644 index 64f24e3..0000000 --- a/hyperlane/hyperlane-addresses.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "anvil1": { - "chain_id": 31337, - "domain_id": 131337, - "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", - "warp_token_type": "collateral", - "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" - }, - "anvil2": { - "chain_id": 31338, - "domain_id": 31338, - "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "warp_token_type": "synthetic", - "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" - }, - "celestiadev": { - "domain_id": 69420, - "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", - "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" - } -} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json deleted file mode 100644 index ae2a37b..0000000 --- a/hyperlane/hyperlane-cosmosnative.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", - "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", - "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" -} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml deleted file mode 100644 index 86b298a..0000000 --- a/hyperlane/registry/chains/anvil1/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" -merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" -testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" -validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml deleted file mode 100644 index ad2a018..0000000 --- a/hyperlane/registry/chains/anvil2/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" -proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" -staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml deleted file mode 100644 index 4ad5a1b..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# yaml-language-server: $schema=../schema.json -tokens: - - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" - chainName: anvil1 - collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - connections: - - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 - decimals: 6 - name: USDC - standard: EvmHypCollateral - symbol: USDC - - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" - chainName: anvil2 - connections: - - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 - decimals: 6 - name: USDC - standard: EvmHypSynthetic - symbol: USDC diff --git a/rebalancer/src/client.rs b/rebalancer/src/client.rs index 74e8f54..6046944 100644 --- a/rebalancer/src/client.rs +++ b/rebalancer/src/client.rs @@ -80,6 +80,7 @@ pub struct SubmittedTransfer { pub struct ChainClient { provider: DefaultProvider, + account: Address, } impl ChainClient { @@ -105,11 +106,12 @@ impl ChainClient { ); } + let account = signer.address; let provider = ProviderBuilder::new() .wallet(signer.wallet) .connect_http(rpc_url); - Ok(Self { provider }) + Ok(Self { provider, account }) } pub async fn token_balance(&self, token: Address, account: Address) -> Result { @@ -152,18 +154,28 @@ impl ChainClient { .context("Failed to query pending account nonce") } + async fn pending_nonce(&self) -> Result { + self.provider + .get_transaction_count(self.account) + .block_id(BlockId::Number(BlockNumberOrTag::Pending)) + .await + .context("Failed to fetch pending nonce") + } + pub async fn approve_erc20( &self, token: Address, spender: Address, amount: U256, ) -> Result { + let nonce = self.pending_nonce().await?; let call = IERC20::approveCall { spender, amount }; let call_data = Bytes::from(call.abi_encode()); let tx = TransactionRequest::default() .to(token) - .input(call_data.into()); + .input(call_data.into()) + .nonce(nonce); let pending = self .provider @@ -235,6 +247,7 @@ impl ChainClient { amount: U256, msg_value: U256, ) -> Result { + let nonce = self.pending_nonce().await?; let recipient = address_to_bytes32(destination_recipient); let call = ITokenRouter::transferRemoteCall { _destination: destination_domain_id, @@ -246,7 +259,8 @@ impl ChainClient { let tx = TransactionRequest::default() .to(source_router) .input(call_data.into()) - .value(msg_value); + .value(msg_value) + .nonce(nonce); let (message_id, preview_error) = match self.provider.call(tx.clone()).await { Ok(raw) => { From f8c657b99226667a90fcc26811f3acd92cab9cd8 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 9 Mar 2026 15:45:55 +0100 Subject: [PATCH 06/34] defillama with coingecko fallback --- solver-cli/src/solver/config_gen.rs | 30 ++++++++++------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index de32146..e4dbe78 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -103,21 +103,6 @@ decimals = {} output_oracles.push(format!("{} = [\"{}\"]", chain.chain_id, oracle)); } - // Build mock price pairs from all configured tokens - let mut price_symbols: Vec = state - .chains - .values() - .flat_map(|c| c.tokens.keys().cloned()) - .collect::>() - .into_iter() - .collect(); - price_symbols.sort(); - let mock_prices = price_symbols - .iter() - .map(|sym| format!("\"{}/USD\" = \"1.0\"", sym)) - .collect::>() - .join("\n"); - // Build routes (all-to-all) let mut routes = Vec::new(); for &from_id in &chain_ids { @@ -214,12 +199,17 @@ max_gas_price_gwei = 100 # PRICING # ============================================================================ [pricing] -primary = "mock" +primary = "defillama" +fallbacks = ["coingecko"] + +[pricing.implementations.defillama] +# base_url = "https://coins.llama.fi" # default +# cache_duration_seconds = 60 # default -[pricing.implementations.mock] -# Mock prices for testing (auto-generated from configured tokens) -[pricing.implementations.mock.pair_prices] -{mock_prices} +[pricing.implementations.coingecko] +# api_key = "" # optional, omit for free tier +# cache_duration_seconds = 60 +# rate_limit_delay_ms = 1200 # free tier default # ============================================================================ # SETTLEMENT From 615ec90f6b4cc1ea1bc99eabb17f43192b6ea97d Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 9 Mar 2026 16:31:31 +0100 Subject: [PATCH 07/34] configure bps --- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 +++++++++++++++++++ hyperlane/hyperlane-cosmosnative.json | 8 +++++ .../registry/chains/anvil1/addresses.yaml | 14 +++++++++ .../registry/chains/anvil2/addresses.yaml | 14 +++++++++ .../warp_routes/USDC/warp-config-config.yaml | 19 ++++++++++++ .../warp_routes/USDC/warp-config-deploy.yaml | 15 ++++++++++ solver-cli/src/solver/config_gen.rs | 10 +++++-- 8 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 hyperlane/hyperlane-addresses.json create mode 100644 hyperlane/hyperlane-cosmosnative.json create mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml create mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json new file mode 100644 index 0000000..64f24e3 --- /dev/null +++ b/hyperlane/hyperlane-addresses.json @@ -0,0 +1,29 @@ +{ + "anvil1": { + "chain_id": 31337, + "domain_id": 131337, + "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "warp_token_type": "collateral", + "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "anvil2": { + "chain_id": 31338, + "domain_id": 31338, + "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "warp_token_type": "synthetic", + "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + }, + "celestiadev": { + "domain_id": 69420, + "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" + } +} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json new file mode 100644 index 0000000..ae2a37b --- /dev/null +++ b/hyperlane/hyperlane-cosmosnative.json @@ -0,0 +1,8 @@ +{ + "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", + "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", + "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" +} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml new file mode 100644 index 0000000..86b298a --- /dev/null +++ b/hyperlane/registry/chains/anvil1/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" +merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" +validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml new file mode 100644 index 0000000..ad2a018 --- /dev/null +++ b/hyperlane/registry/chains/anvil2/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" +proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml new file mode 100644 index 0000000..4ad5a1b --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../schema.json +tokens: + - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + chainName: anvil1 + collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + connections: + - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 + decimals: 6 + name: USDC + standard: EvmHypCollateral + symbol: USDC + - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + chainName: anvil2 + connections: + - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 + decimals: 6 + name: USDC + standard: EvmHypSynthetic + symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml new file mode 100644 index 0000000..dec3a6a --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml @@ -0,0 +1,15 @@ +anvil1: + decimals: 6 + mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + type: collateral +anvil2: + decimals: 6 + mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + type: synthetic diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index e4dbe78..851f0e2 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -123,7 +123,9 @@ decimals = {} [solver] id = "{solver_id}" -min_profitability_pct = -1000.0 # Allow massive losses for testing +min_profitability_pct = 0.0 +commission_bps = 20 +rate_buffer_bps = 15 monitoring_timeout_seconds = 28800 # ============================================================================ @@ -516,7 +518,11 @@ poll_interval_seconds = 3 crate::utils::SolverSignerConfig::Env => { let raw = std::env::var("SOLVER_PRIVATE_KEY") .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; - let key = if raw.starts_with("0x") { raw } else { format!("0x{raw}") }; + let key = if raw.starts_with("0x") { + raw + } else { + format!("0x{raw}") + }; serde_json::json!({ "type": "hexKey", "key": key }) } }; From c9ba3b9bee00fc64da8291fc56fec64d41910471 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 9 Mar 2026 16:51:17 +0100 Subject: [PATCH 08/34] improve error handling --- frontend/server.js | 23 ++++++++++++++++++++- frontend/src/App.tsx | 49 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/frontend/server.js b/frontend/server.js index 9f7f3fe..3afc83f 100644 --- a/frontend/server.js +++ b/frontend/server.js @@ -221,6 +221,24 @@ function isOriginChain(chainName, hypAddresses) { return data && data.mock_usdc; } +// ── Error helpers ──────────────────────────────────────────────────────────── + +/** + * Map raw aggregator error messages to user-friendly descriptions. + * The aggregator returns generic strings; we enrich them with actionable context. + */ +function mapAggregatorQuoteError(msg) { + if (typeof msg !== 'string') return JSON.stringify(msg); + const lower = msg.toLowerCase(); + if (lower.includes('all solvers failed')) { + return 'SOLVER_REJECTED: No solver could fill this transfer — the amount is likely too small to cover gas and bridging fees. Try a larger amount.'; + } + if (lower.includes('no solvers available')) { + return 'SOLVER_OFFLINE: No solvers are available for this route. Make sure the solver is running (make solver).'; + } + return msg; +} + // ── Express App ────────────────────────────────────────────────────────────── const app = express(); @@ -718,7 +736,10 @@ app.post('/api/quote', async (req, res) => { }); const data = await response.json(); - if (!response.ok) throw new Error(data.message || data.error || JSON.stringify(data)); + if (!response.ok) { + const rawMsg = data.message || data.error || JSON.stringify(data); + throw new Error(mapAggregatorQuoteError(rawMsg)); + } console.log(`[quote] Got ${data.quotes?.length ?? 0} quotes`); res.json(data); } catch (err) { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ed0d884..822e13e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -162,7 +162,13 @@ export default function App() { const raw = Math.round(parseFloat(amount) * 10 ** selectedTokenDecimals).toString() const resp = await api.quote(fromId, toId, raw, asset, isConnected && connectedAddress ? connectedAddress : undefined) - if (!resp.quotes?.length) throw new Error('No quotes returned. Is the solver running?') + if (!resp.quotes?.length) { + const meta = (resp as any).metadata + const allFailed = meta && meta.solvers_queried > 0 && meta.solvers_responded_success === 0 + throw new Error(allFailed + ? 'SOLVER_REJECTED: No solver could fill this transfer — the amount is likely too small to cover gas and bridging fees. Try a larger amount.' + : 'No quotes returned. Is the solver running? (make solver)') + } setQuote(resp.quotes[0]); setStep('quoted') } catch (err: any) { setError(err.message); setStep('error') } } @@ -634,15 +640,38 @@ export default function App() { })()} {/* Error */} - {error && ( -
- - - - -

{error}

-
- )} + {error && (() => { + const isSolverRejected = error.startsWith('SOLVER_REJECTED:') + const isSolverOffline = error.startsWith('SOLVER_OFFLINE:') + const displayMsg = error.replace(/^SOLVER_(REJECTED|OFFLINE):\s*/, '') + return ( +
+ + + + +
+

+ {displayMsg} +

+ {isSolverRejected && ( +

+ Tip: increase the amount or check that the solver has sufficient inventory. +

+ )} + {isSolverOffline && ( +

+ Run make solver to start the solver. +

+ )} +
+
+ ) + })()} {/* CTA */} {routeType === 'fast' ? ( From 75965c180a3158f92d0dc53d221cf20a3be7e1be Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 9 Mar 2026 16:53:22 +0100 Subject: [PATCH 09/34] cleanup --- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 ------------------- hyperlane/hyperlane-cosmosnative.json | 8 ----- .../registry/chains/anvil1/addresses.yaml | 14 --------- .../registry/chains/anvil2/addresses.yaml | 14 --------- .../warp_routes/USDC/warp-config-config.yaml | 19 ------------ .../warp_routes/USDC/warp-config-deploy.yaml | 15 ---------- 7 files changed, 1 insertion(+), 100 deletions(-) delete mode 100644 hyperlane/hyperlane-addresses.json delete mode 100644 hyperlane/hyperlane-cosmosnative.json delete mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml delete mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json deleted file mode 100644 index 64f24e3..0000000 --- a/hyperlane/hyperlane-addresses.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "anvil1": { - "chain_id": 31337, - "domain_id": 131337, - "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", - "warp_token_type": "collateral", - "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" - }, - "anvil2": { - "chain_id": 31338, - "domain_id": 31338, - "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "warp_token_type": "synthetic", - "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" - }, - "celestiadev": { - "domain_id": 69420, - "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", - "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" - } -} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json deleted file mode 100644 index ae2a37b..0000000 --- a/hyperlane/hyperlane-cosmosnative.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", - "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", - "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" -} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml deleted file mode 100644 index 86b298a..0000000 --- a/hyperlane/registry/chains/anvil1/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" -merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" -testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" -validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml deleted file mode 100644 index ad2a018..0000000 --- a/hyperlane/registry/chains/anvil2/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" -proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" -staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml deleted file mode 100644 index 4ad5a1b..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# yaml-language-server: $schema=../schema.json -tokens: - - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" - chainName: anvil1 - collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - connections: - - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 - decimals: 6 - name: USDC - standard: EvmHypCollateral - symbol: USDC - - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" - chainName: anvil2 - connections: - - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 - decimals: 6 - name: USDC - standard: EvmHypSynthetic - symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml deleted file mode 100644 index dec3a6a..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml +++ /dev/null @@ -1,15 +0,0 @@ -anvil1: - decimals: 6 - mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - type: collateral -anvil2: - decimals: 6 - mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - type: synthetic From 26179dc303f9a7cb4dd73fbcce26eb176db3215d Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 9 Mar 2026 16:53:37 +0100 Subject: [PATCH 10/34] format --- Cargo.toml | 4 +-- oracle-operator/Cargo.toml | 8 ++--- oracle-operator/src/operator.rs | 17 +++++------ rebalancer/src/client.rs | 16 ++++------ solver-cli/Cargo.toml | 4 +-- solver-cli/src/commands/account.rs | 12 ++++++-- solver-cli/src/commands/chain.rs | 8 ++--- solver-cli/src/commands/configure.rs | 6 +++- solver-cli/src/commands/fund.rs | 6 +++- solver-cli/src/deployment/deployer.rs | 6 +++- solver-cli/src/rebalancer/config_gen.rs | 17 +++++++---- solver-cli/src/utils/env.rs | 15 +++++++--- solver-settlement/Cargo.toml | 8 ++--- solver-settlement/src/centralized.rs | 40 ++++++++++++------------- 14 files changed, 97 insertions(+), 70 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 67c266e..db1494e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [workspace] members = [ - "solver-cli", - "solver-settlement", "oracle-operator", "rebalancer", + "solver-cli", + "solver-settlement", ] resolver = "2" diff --git a/oracle-operator/Cargo.toml b/oracle-operator/Cargo.toml index e87c30c..f1b19b3 100644 --- a/oracle-operator/Cargo.toml +++ b/oracle-operator/Cargo.toml @@ -21,6 +21,10 @@ alloy-sol-types = "1.0" # Error handling anyhow = "1.0" +# AWS KMS signing +aws-config = "1" +aws-sdk-kms = "1" + # Utils hex = "0.4" @@ -31,10 +35,6 @@ reqwest = "0.12" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -# AWS KMS signing -aws-config = "1" -aws-sdk-kms = "1" - # Crypto sha3 = "0.10" thiserror = "2.0" diff --git a/oracle-operator/src/operator.rs b/oracle-operator/src/operator.rs index 61cc390..a9d7697 100644 --- a/oracle-operator/src/operator.rs +++ b/oracle-operator/src/operator.rs @@ -446,15 +446,14 @@ impl OracleOperator { })?, }; - let decoded = - IOutputSettlerSimple::OutputFilled::decode_log(&prim_log).map_err(|e| { - anyhow::anyhow!( - "Failed to decode OutputFilled for order {} on chain {}: {}", - hex::encode(order_id), - source_chain_id, - e - ) - })?; + let decoded = IOutputSettlerSimple::OutputFilled::decode_log(&prim_log).map_err(|e| { + anyhow::anyhow!( + "Failed to decode OutputFilled for order {} on chain {}: {}", + hex::encode(order_id), + source_chain_id, + e + ) + })?; let output = &decoded.output; let application_id = output.settler.0; diff --git a/rebalancer/src/client.rs b/rebalancer/src/client.rs index 6046944..8cfabda 100644 --- a/rebalancer/src/client.rs +++ b/rebalancer/src/client.rs @@ -177,16 +177,12 @@ impl ChainClient { .input(call_data.into()) .nonce(nonce); - let pending = self - .provider - .send_transaction(tx) - .await - .with_context(|| { - format!( - "Failed to send ERC20 approve tx: token={} spender={} amount={}", - token, spender, amount - ) - })?; + let pending = self.provider.send_transaction(tx).await.with_context(|| { + format!( + "Failed to send ERC20 approve tx: token={} spender={} amount={}", + token, spender, amount + ) + })?; Ok(*pending.tx_hash()) } diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index c35bd4d..749e0be 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -85,9 +85,9 @@ uuid = { version = "1.6", features = ["v4", "serde"] } # Unix process management [target.'cfg(unix)'.dependencies] alloy-primitives = { version = "1.0.37", features = ["std", "serde"], optional = true } -alloy-sol-types = { version = "1.0.37", optional = true } alloy-provider = { version = "1.0", optional = true } alloy-rpc-types = { version = "1.0", optional = true } +alloy-sol-types = { version = "1.0.37", optional = true } futures = { version = "0.3", optional = true } nix = { version = "0.28", features = ["signal", "process"] } rebalancer = { path = "../rebalancer" } @@ -108,7 +108,7 @@ solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b assert_cmd = "2.0" mockall = "0.13" predicates = "3.0" -tempfile = "3.8" solver-settlement = { path = "../solver-settlement", features = ["testing"] } solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", features = ["testing"] } +tempfile = "3.8" diff --git a/solver-cli/src/commands/account.rs b/solver-cli/src/commands/account.rs index faa834d..da0daec 100644 --- a/solver-cli/src/commands/account.rs +++ b/solver-cli/src/commands/account.rs @@ -34,7 +34,11 @@ impl AccountCommand { async fn print_solver_address() -> Result<()> { match SolverSignerConfig::from_env()? { - SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + SolverSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { use alloy::signers::aws::AwsSigner; use alloy::signers::Signer; use aws_sdk_kms::config::Region; @@ -56,7 +60,11 @@ async fn print_solver_address() -> Result<()> { let raw = env::var("SOLVER_PRIVATE_KEY") .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; - let pk = if raw.starts_with("0x") { raw } else { format!("0x{raw}") }; + let pk = if raw.starts_with("0x") { + raw + } else { + format!("0x{raw}") + }; let addr = ChainClient::address_from_pk(&pk)?; println!("{addr:?}"); } diff --git a/solver-cli/src/commands/chain.rs b/solver-cli/src/commands/chain.rs index 4940f40..8041c02 100644 --- a/solver-cli/src/commands/chain.rs +++ b/solver-cli/src/commands/chain.rs @@ -210,16 +210,16 @@ impl ChainCommand { } // Build contracts struct - let hyperlane = warp_token.as_ref().map(|addr| { - crate::state::HyperlaneAddresses { + let hyperlane = warp_token + .as_ref() + .map(|addr| crate::state::HyperlaneAddresses { mailbox: None, merkle_tree_hook: None, validator_announce: None, igp: None, warp_token: Some(addr.clone()), warp_token_type: Some("collateral".to_string()), - } - }); + }); let contracts = ContractAddresses { input_settler_escrow: Some(input_settler.clone()), diff --git a/solver-cli/src/commands/configure.rs b/solver-cli/src/commands/configure.rs index 1db9f8d..efdd327 100644 --- a/solver-cli/src/commands/configure.rs +++ b/solver-cli/src/commands/configure.rs @@ -50,7 +50,11 @@ impl ConfigureCommand { // Derive solver address based on SOLVER_SIGNER_TYPE (mirrors oracle-operator pattern). let solver_address = match crate::utils::SolverSignerConfig::from_env()? { - crate::utils::SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + crate::utils::SolverSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { use alloy::signers::aws::AwsSigner; use alloy::signers::Signer; use aws_sdk_kms::config::Region; diff --git a/solver-cli/src/commands/fund.rs b/solver-cli/src/commands/fund.rs index 598b0e5..97f863d 100644 --- a/solver-cli/src/commands/fund.rs +++ b/solver-cli/src/commands/fund.rs @@ -50,7 +50,11 @@ impl FundCommand { // Derive solver address from whichever signer is configured (env key or AWS KMS). let solver_address = match crate::utils::SolverSignerConfig::from_env()? { - crate::utils::SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + crate::utils::SolverSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { use alloy::signers::aws::AwsSigner; use alloy::signers::Signer; use aws_sdk_kms::config::Region; diff --git a/solver-cli/src/deployment/deployer.rs b/solver-cli/src/deployment/deployer.rs index 8da9889..a408482 100644 --- a/solver-cli/src/deployment/deployer.rs +++ b/solver-cli/src/deployment/deployer.rs @@ -92,7 +92,11 @@ impl Deployer { // Derive operator address from ORACLE_SIGNER_TYPE (env key or AWS KMS). let operator_address = match crate::utils::OracleSignerConfig::from_env()? { - crate::utils::OracleSignerConfig::AwsKms { key_id, region, endpoint } => { + crate::utils::OracleSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { use alloy::signers::aws::AwsSigner; use alloy::signers::Signer; use aws_sdk_kms::config::Region; diff --git a/solver-cli/src/rebalancer/config_gen.rs b/solver-cli/src/rebalancer/config_gen.rs index 810da53..b6e36c1 100644 --- a/solver-cli/src/rebalancer/config_gen.rs +++ b/solver-cli/src/rebalancer/config_gen.rs @@ -211,7 +211,10 @@ fn collect_assets(state: &SolverState) -> Result> { ); } - let chain_ids: Vec = entries.iter().map(|(chain_id, _, _, _)| *chain_id).collect(); + let chain_ids: Vec = entries + .iter() + .map(|(chain_id, _, _, _)| *chain_id) + .collect(); let weights = equal_weight_distribution(&chain_ids, 1_000_000); let min_weights: Vec<(u64, f64)> = weights .iter() @@ -228,11 +231,13 @@ fn collect_assets(state: &SolverState) -> Result> { decimals: expected_decimals, tokens: entries .into_iter() - .map(|(chain_id, address, _, collateral_token)| RebalancerTokenEntry { - chain_id, - address, - collateral_token, - }) + .map( + |(chain_id, address, _, collateral_token)| RebalancerTokenEntry { + chain_id, + address, + collateral_token, + }, + ) .collect(), weights, min_weights, diff --git a/solver-cli/src/utils/env.rs b/solver-cli/src/utils/env.rs index 570d862..ba821de 100644 --- a/solver-cli/src/utils/env.rs +++ b/solver-cli/src/utils/env.rs @@ -153,7 +153,11 @@ impl SolverSignerConfig { let region = env::var("SOLVER_KMS_REGION") .context("Missing required environment variable: SOLVER_KMS_REGION")?; let endpoint = env::var("SOLVER_KMS_ENDPOINT").ok(); - Ok(Self::AwsKms { key_id, region, endpoint }) + Ok(Self::AwsKms { + key_id, + region, + endpoint, + }) } _ => Ok(Self::Env), } @@ -173,8 +177,7 @@ pub enum RebalancerSignerConfig { impl RebalancerSignerConfig { pub fn from_env() -> Result { - let signer_type = - env::var("REBALANCER_SIGNER_TYPE").unwrap_or_else(|_| "env".to_string()); + let signer_type = env::var("REBALANCER_SIGNER_TYPE").unwrap_or_else(|_| "env".to_string()); match signer_type.as_str() { "aws_kms" => { let key_id = env::var("REBALANCER_KMS_KEY_ID") @@ -213,7 +216,11 @@ impl OracleSignerConfig { let region = env::var("ORACLE_KMS_REGION") .context("Missing required environment variable: ORACLE_KMS_REGION")?; let endpoint = env::var("ORACLE_KMS_ENDPOINT").ok(); - Ok(Self::AwsKms { key_id, region, endpoint }) + Ok(Self::AwsKms { + key_id, + region, + endpoint, + }) } _ => Ok(Self::Env), } diff --git a/solver-settlement/Cargo.toml b/solver-settlement/Cargo.toml index 313310c..57d1337 100644 --- a/solver-settlement/Cargo.toml +++ b/solver-settlement/Cargo.toml @@ -7,16 +7,16 @@ edition = "2024" testing = ["upstream-solver-settlement/testing"] [dependencies] -upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", features = ["oif-interfaces"] } alloy-primitives = { version = "1.0.37", features = ["std", "serde"] } alloy-provider = { version = "1.0" } alloy-rpc-types = { version = "1.0" } alloy-sol-types = { version = "1.0.37" } async-trait = "0.1" -sha3 = "0.10" serde_json = "1.0" +sha3 = "0.10" +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", features = ["oif-interfaces"] } tokio = { version = "1", features = ["rt"] } toml = "0.9" tracing = "0.1" +upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" } diff --git a/solver-settlement/src/centralized.rs b/solver-settlement/src/centralized.rs index bacf09f..a8c06c7 100644 --- a/solver-settlement/src/centralized.rs +++ b/solver-settlement/src/centralized.rs @@ -1,19 +1,19 @@ -use alloy_primitives::{hex, Address as AlloyAddress, FixedBytes, U256}; +use alloy_primitives::{Address as AlloyAddress, FixedBytes, U256, hex}; use alloy_provider::{DynProvider, Provider}; -use alloy_sol_types::{sol, SolCall}; +use alloy_sol_types::{SolCall, sol}; use async_trait::async_trait; use sha3::{Digest, Keccak256}; -use upstream_solver_settlement::{ - utils::parse_oracle_config, OracleConfig, SettlementError, SettlementInterface, -}; use solver_types::{ - create_http_provider, standards::eip7683::Eip7683OrderData, with_0x_prefix, Address, - ConfigSchema, Field, FieldType, FillProof, NetworksConfig, Order, ProviderError, Schema, - Transaction, TransactionHash, TransactionReceipt, TransactionType, + Address, ConfigSchema, Field, FieldType, FillProof, NetworksConfig, Order, ProviderError, + Schema, Transaction, TransactionHash, TransactionReceipt, TransactionType, + create_http_provider, standards::eip7683::Eip7683OrderData, with_0x_prefix, }; use std::collections::HashMap; use std::sync::Arc; use tracing::{debug, info, warn}; +use upstream_solver_settlement::{ + OracleConfig, SettlementError, SettlementInterface, utils::parse_oracle_config, +}; sol! { interface ICentralizedOracle { @@ -376,7 +376,7 @@ impl SettlementInterface for CentralizedSettlement { .logs() .iter() .map(|log| solver_types::Log { - address: solver_types::Address(log.address().0 .0.to_vec()), + address: solver_types::Address(log.address().0.0.to_vec()), topics: log .topics() .iter() @@ -462,13 +462,13 @@ impl SettlementInterface for CentralizedSettlement { }; debug!( - "Checking isProven: chain={}, remote_chain={}, remote_oracle=0x{}, settler=0x{}, hash=0x{}", - origin_chain_id, - destination_chain_id, - hex::encode(remote_oracle), - hex::encode(output_settler), - hex::encode(payload_hash) - ); + "Checking isProven: chain={}, remote_chain={}, remote_oracle=0x{}, settler=0x{}, hash=0x{}", + origin_chain_id, + destination_chain_id, + hex::encode(remote_oracle), + hex::encode(output_settler), + hex::encode(payload_hash) + ); match self .check_is_proven( @@ -504,10 +504,10 @@ impl SettlementInterface for CentralizedSettlement { let payload_hash = self.compute_payload_hash(order, &solver_array, timestamp)?; info!( - "Fill confirmed for order {}, waiting for oracle operator attestation (payload_hash=0x{})", - order.id, - hex::encode(payload_hash) - ); + "Fill confirmed for order {}, waiting for oracle operator attestation (payload_hash=0x{})", + order.id, + hex::encode(payload_hash) + ); Ok(None) } From d9f1f9b8c707c6bb0f666cd647cb3bed96904efd Mon Sep 17 00:00:00 2001 From: Damian Nolan Date: Tue, 10 Mar 2026 10:38:56 +0100 Subject: [PATCH 11/34] feat: add rust linting/formatting and github CI job --- .github/workflows/rust.yml | 39 ++++++++++++++++++++++++ .rust-toolchain.toml | 4 +++ Makefile | 24 +++++++++++++++ README.md | 20 ++++++++++--- clippy.toml | 1 + oracle-operator/src/operator.rs | 19 ++++++------ rebalancer/src/client.rs | 16 ++++------ solver-cli/src/commands/account.rs | 12 ++++++-- solver-cli/src/commands/chain.rs | 8 ++--- solver-cli/src/commands/configure.rs | 6 +++- solver-cli/src/commands/fund.rs | 6 +++- solver-cli/src/deployment/deployer.rs | 6 +++- solver-cli/src/rebalancer/config_gen.rs | 17 +++++++---- solver-cli/src/solver/config_gen.rs | 6 +++- solver-cli/src/utils/env.rs | 15 +++++++--- solver-settlement/src/centralized.rs | 40 ++++++++++++------------- 16 files changed, 175 insertions(+), 64 deletions(-) create mode 100644 .github/workflows/rust.yml create mode 100644 .rust-toolchain.toml create mode 100644 clippy.toml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..a77e39f --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,39 @@ +name: Rust CI + +on: + pull_request: + push: + +env: + CARGO_TERM_COLOR: always + +jobs: + fmt: + name: rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Check formatting + run: make fmt-check + + clippy: + name: clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Run clippy + run: make lint + + test: + name: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Run workspace tests + run: make test-rust diff --git a/.rust-toolchain.toml b/.rust-toolchain.toml new file mode 100644 index 0000000..03199c4 --- /dev/null +++ b/.rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.91.1" +components = ["rustfmt", "clippy"] +profile = "minimal" diff --git a/Makefile b/Makefile index f97a592..5e2b332 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,30 @@ build: @cd solver-cli && cargo build --release --features solver-runtime .PHONY: build +## fmt: Format all Rust workspace crates with rustfmt +fmt: + @cargo fmt --all +.PHONY: fmt + +## fmt-check: Check Rust formatting across the workspace +fmt-check: + @cargo fmt --all --check +.PHONY: fmt-check + +## lint: Run clippy across the Rust workspace +lint: + @cargo clippy --workspace --all-targets --all-features -- -D warnings +.PHONY: lint + +## test-rust: Run Rust workspace tests +test-rust: + @cargo test --workspace --all-targets +.PHONY: test-rust + +## ci-rust: Run the full Rust quality suite locally +ci-rust: fmt-check lint test-rust +.PHONY: ci-rust + # ============================================================================ # Docker Image Builds # ============================================================================ diff --git a/README.md b/README.md index d0aced5..6537cff 100644 --- a/README.md +++ b/README.md @@ -288,11 +288,23 @@ Ensure your solver address has native tokens on all chains for gas. ## Development ```bash -# Build CLI -cd solver-cli && cargo build --release +# Rustup will honor .rust-toolchain.toml automatically in this repo. +# To preinstall it explicitly: +rustup toolchain install 1.91.1 --component rustfmt --component clippy -# Run tests -cd solver-cli && cargo test +# Format all workspace crates +make fmt + +# Run the same Rust quality checks as CI +make ci-rust + +# Build the main CLI +make build + +# Run individual checks +make fmt-check +make lint +make test-rust # Build contracts cd oif/oif-contracts && forge build diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..8d29173 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.91.1" diff --git a/oracle-operator/src/operator.rs b/oracle-operator/src/operator.rs index 61cc390..b24759a 100644 --- a/oracle-operator/src/operator.rs +++ b/oracle-operator/src/operator.rs @@ -234,7 +234,7 @@ impl OracleOperator { // Periodically save state to disk poll_count += 1; - if poll_count % save_interval == 0 { + if poll_count.is_multiple_of(save_interval) { let mut state = self.state.lock().await; if let Err(e) = state.save_if_dirty() { error!("Failed to save state: {}", e); @@ -446,15 +446,14 @@ impl OracleOperator { })?, }; - let decoded = - IOutputSettlerSimple::OutputFilled::decode_log(&prim_log).map_err(|e| { - anyhow::anyhow!( - "Failed to decode OutputFilled for order {} on chain {}: {}", - hex::encode(order_id), - source_chain_id, - e - ) - })?; + let decoded = IOutputSettlerSimple::OutputFilled::decode_log(&prim_log).map_err(|e| { + anyhow::anyhow!( + "Failed to decode OutputFilled for order {} on chain {}: {}", + hex::encode(order_id), + source_chain_id, + e + ) + })?; let output = &decoded.output; let application_id = output.settler.0; diff --git a/rebalancer/src/client.rs b/rebalancer/src/client.rs index 6046944..8cfabda 100644 --- a/rebalancer/src/client.rs +++ b/rebalancer/src/client.rs @@ -177,16 +177,12 @@ impl ChainClient { .input(call_data.into()) .nonce(nonce); - let pending = self - .provider - .send_transaction(tx) - .await - .with_context(|| { - format!( - "Failed to send ERC20 approve tx: token={} spender={} amount={}", - token, spender, amount - ) - })?; + let pending = self.provider.send_transaction(tx).await.with_context(|| { + format!( + "Failed to send ERC20 approve tx: token={} spender={} amount={}", + token, spender, amount + ) + })?; Ok(*pending.tx_hash()) } diff --git a/solver-cli/src/commands/account.rs b/solver-cli/src/commands/account.rs index faa834d..da0daec 100644 --- a/solver-cli/src/commands/account.rs +++ b/solver-cli/src/commands/account.rs @@ -34,7 +34,11 @@ impl AccountCommand { async fn print_solver_address() -> Result<()> { match SolverSignerConfig::from_env()? { - SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + SolverSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { use alloy::signers::aws::AwsSigner; use alloy::signers::Signer; use aws_sdk_kms::config::Region; @@ -56,7 +60,11 @@ async fn print_solver_address() -> Result<()> { let raw = env::var("SOLVER_PRIVATE_KEY") .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; - let pk = if raw.starts_with("0x") { raw } else { format!("0x{raw}") }; + let pk = if raw.starts_with("0x") { + raw + } else { + format!("0x{raw}") + }; let addr = ChainClient::address_from_pk(&pk)?; println!("{addr:?}"); } diff --git a/solver-cli/src/commands/chain.rs b/solver-cli/src/commands/chain.rs index 4940f40..8041c02 100644 --- a/solver-cli/src/commands/chain.rs +++ b/solver-cli/src/commands/chain.rs @@ -210,16 +210,16 @@ impl ChainCommand { } // Build contracts struct - let hyperlane = warp_token.as_ref().map(|addr| { - crate::state::HyperlaneAddresses { + let hyperlane = warp_token + .as_ref() + .map(|addr| crate::state::HyperlaneAddresses { mailbox: None, merkle_tree_hook: None, validator_announce: None, igp: None, warp_token: Some(addr.clone()), warp_token_type: Some("collateral".to_string()), - } - }); + }); let contracts = ContractAddresses { input_settler_escrow: Some(input_settler.clone()), diff --git a/solver-cli/src/commands/configure.rs b/solver-cli/src/commands/configure.rs index 1db9f8d..efdd327 100644 --- a/solver-cli/src/commands/configure.rs +++ b/solver-cli/src/commands/configure.rs @@ -50,7 +50,11 @@ impl ConfigureCommand { // Derive solver address based on SOLVER_SIGNER_TYPE (mirrors oracle-operator pattern). let solver_address = match crate::utils::SolverSignerConfig::from_env()? { - crate::utils::SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + crate::utils::SolverSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { use alloy::signers::aws::AwsSigner; use alloy::signers::Signer; use aws_sdk_kms::config::Region; diff --git a/solver-cli/src/commands/fund.rs b/solver-cli/src/commands/fund.rs index 598b0e5..97f863d 100644 --- a/solver-cli/src/commands/fund.rs +++ b/solver-cli/src/commands/fund.rs @@ -50,7 +50,11 @@ impl FundCommand { // Derive solver address from whichever signer is configured (env key or AWS KMS). let solver_address = match crate::utils::SolverSignerConfig::from_env()? { - crate::utils::SolverSignerConfig::AwsKms { key_id, region, endpoint } => { + crate::utils::SolverSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { use alloy::signers::aws::AwsSigner; use alloy::signers::Signer; use aws_sdk_kms::config::Region; diff --git a/solver-cli/src/deployment/deployer.rs b/solver-cli/src/deployment/deployer.rs index 8da9889..a408482 100644 --- a/solver-cli/src/deployment/deployer.rs +++ b/solver-cli/src/deployment/deployer.rs @@ -92,7 +92,11 @@ impl Deployer { // Derive operator address from ORACLE_SIGNER_TYPE (env key or AWS KMS). let operator_address = match crate::utils::OracleSignerConfig::from_env()? { - crate::utils::OracleSignerConfig::AwsKms { key_id, region, endpoint } => { + crate::utils::OracleSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { use alloy::signers::aws::AwsSigner; use alloy::signers::Signer; use aws_sdk_kms::config::Region; diff --git a/solver-cli/src/rebalancer/config_gen.rs b/solver-cli/src/rebalancer/config_gen.rs index 810da53..b6e36c1 100644 --- a/solver-cli/src/rebalancer/config_gen.rs +++ b/solver-cli/src/rebalancer/config_gen.rs @@ -211,7 +211,10 @@ fn collect_assets(state: &SolverState) -> Result> { ); } - let chain_ids: Vec = entries.iter().map(|(chain_id, _, _, _)| *chain_id).collect(); + let chain_ids: Vec = entries + .iter() + .map(|(chain_id, _, _, _)| *chain_id) + .collect(); let weights = equal_weight_distribution(&chain_ids, 1_000_000); let min_weights: Vec<(u64, f64)> = weights .iter() @@ -228,11 +231,13 @@ fn collect_assets(state: &SolverState) -> Result> { decimals: expected_decimals, tokens: entries .into_iter() - .map(|(chain_id, address, _, collateral_token)| RebalancerTokenEntry { - chain_id, - address, - collateral_token, - }) + .map( + |(chain_id, address, _, collateral_token)| RebalancerTokenEntry { + chain_id, + address, + collateral_token, + }, + ) .collect(), weights, min_weights, diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index de32146..6cfca39 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -526,7 +526,11 @@ poll_interval_seconds = 3 crate::utils::SolverSignerConfig::Env => { let raw = std::env::var("SOLVER_PRIVATE_KEY") .context("Missing required environment variable: SOLVER_PRIVATE_KEY")?; - let key = if raw.starts_with("0x") { raw } else { format!("0x{raw}") }; + let key = if raw.starts_with("0x") { + raw + } else { + format!("0x{raw}") + }; serde_json::json!({ "type": "hexKey", "key": key }) } }; diff --git a/solver-cli/src/utils/env.rs b/solver-cli/src/utils/env.rs index 570d862..ba821de 100644 --- a/solver-cli/src/utils/env.rs +++ b/solver-cli/src/utils/env.rs @@ -153,7 +153,11 @@ impl SolverSignerConfig { let region = env::var("SOLVER_KMS_REGION") .context("Missing required environment variable: SOLVER_KMS_REGION")?; let endpoint = env::var("SOLVER_KMS_ENDPOINT").ok(); - Ok(Self::AwsKms { key_id, region, endpoint }) + Ok(Self::AwsKms { + key_id, + region, + endpoint, + }) } _ => Ok(Self::Env), } @@ -173,8 +177,7 @@ pub enum RebalancerSignerConfig { impl RebalancerSignerConfig { pub fn from_env() -> Result { - let signer_type = - env::var("REBALANCER_SIGNER_TYPE").unwrap_or_else(|_| "env".to_string()); + let signer_type = env::var("REBALANCER_SIGNER_TYPE").unwrap_or_else(|_| "env".to_string()); match signer_type.as_str() { "aws_kms" => { let key_id = env::var("REBALANCER_KMS_KEY_ID") @@ -213,7 +216,11 @@ impl OracleSignerConfig { let region = env::var("ORACLE_KMS_REGION") .context("Missing required environment variable: ORACLE_KMS_REGION")?; let endpoint = env::var("ORACLE_KMS_ENDPOINT").ok(); - Ok(Self::AwsKms { key_id, region, endpoint }) + Ok(Self::AwsKms { + key_id, + region, + endpoint, + }) } _ => Ok(Self::Env), } diff --git a/solver-settlement/src/centralized.rs b/solver-settlement/src/centralized.rs index bacf09f..a8c06c7 100644 --- a/solver-settlement/src/centralized.rs +++ b/solver-settlement/src/centralized.rs @@ -1,19 +1,19 @@ -use alloy_primitives::{hex, Address as AlloyAddress, FixedBytes, U256}; +use alloy_primitives::{Address as AlloyAddress, FixedBytes, U256, hex}; use alloy_provider::{DynProvider, Provider}; -use alloy_sol_types::{sol, SolCall}; +use alloy_sol_types::{SolCall, sol}; use async_trait::async_trait; use sha3::{Digest, Keccak256}; -use upstream_solver_settlement::{ - utils::parse_oracle_config, OracleConfig, SettlementError, SettlementInterface, -}; use solver_types::{ - create_http_provider, standards::eip7683::Eip7683OrderData, with_0x_prefix, Address, - ConfigSchema, Field, FieldType, FillProof, NetworksConfig, Order, ProviderError, Schema, - Transaction, TransactionHash, TransactionReceipt, TransactionType, + Address, ConfigSchema, Field, FieldType, FillProof, NetworksConfig, Order, ProviderError, + Schema, Transaction, TransactionHash, TransactionReceipt, TransactionType, + create_http_provider, standards::eip7683::Eip7683OrderData, with_0x_prefix, }; use std::collections::HashMap; use std::sync::Arc; use tracing::{debug, info, warn}; +use upstream_solver_settlement::{ + OracleConfig, SettlementError, SettlementInterface, utils::parse_oracle_config, +}; sol! { interface ICentralizedOracle { @@ -376,7 +376,7 @@ impl SettlementInterface for CentralizedSettlement { .logs() .iter() .map(|log| solver_types::Log { - address: solver_types::Address(log.address().0 .0.to_vec()), + address: solver_types::Address(log.address().0.0.to_vec()), topics: log .topics() .iter() @@ -462,13 +462,13 @@ impl SettlementInterface for CentralizedSettlement { }; debug!( - "Checking isProven: chain={}, remote_chain={}, remote_oracle=0x{}, settler=0x{}, hash=0x{}", - origin_chain_id, - destination_chain_id, - hex::encode(remote_oracle), - hex::encode(output_settler), - hex::encode(payload_hash) - ); + "Checking isProven: chain={}, remote_chain={}, remote_oracle=0x{}, settler=0x{}, hash=0x{}", + origin_chain_id, + destination_chain_id, + hex::encode(remote_oracle), + hex::encode(output_settler), + hex::encode(payload_hash) + ); match self .check_is_proven( @@ -504,10 +504,10 @@ impl SettlementInterface for CentralizedSettlement { let payload_hash = self.compute_payload_hash(order, &solver_array, timestamp)?; info!( - "Fill confirmed for order {}, waiting for oracle operator attestation (payload_hash=0x{})", - order.id, - hex::encode(payload_hash) - ); + "Fill confirmed for order {}, waiting for oracle operator attestation (payload_hash=0x{})", + order.id, + hex::encode(payload_hash) + ); Ok(None) } From ac9a19b656507eba3c786a92c9c9c2b30ddb131b Mon Sep 17 00:00:00 2001 From: Damian Nolan Date: Tue, 10 Mar 2026 10:52:39 +0100 Subject: [PATCH 12/34] chore: set recursion limit to inform compiler --- rebalancer/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rebalancer/src/main.rs b/rebalancer/src/main.rs index 62c4ae3..8cf0bde 100644 --- a/rebalancer/src/main.rs +++ b/rebalancer/src/main.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "256"] + use anyhow::Result; use clap::Parser; use rebalancer::run_from_config; From 97876c613ffe8bdeeb6310d4d132417139e54331 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Tue, 10 Mar 2026 14:10:38 +0100 Subject: [PATCH 13/34] remove faucet --- .env.example | 6 ++-- frontend/src/App.tsx | 70 -------------------------------------------- frontend/src/api.ts | 8 ----- 3 files changed, 4 insertions(+), 80 deletions(-) diff --git a/.env.example b/.env.example index 38f62cc..9c82bf2 100644 --- a/.env.example +++ b/.env.example @@ -58,14 +58,16 @@ SOLVER_PRIVATE_KEY=redacted #SOLVER_KMS_REGION=us-east-1 # ANVIL1_PK - Deployer for local Anvil chain 1 (Anvil account #0) -# The Anvil default key is fine here (local chain only) +# This account has 10,000 ETH and is minted USDC during `make deploy`. +# The Anvil default key is fine here (local chain only). ANVIL1_PK=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # ANVIL2_PK - Deployer for local Anvil chain 2 (Anvil account #0 - same as ANVIL1_PK) # Using the same key since both are local Anvil instances ANVIL2_PK=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -# USER_PK - Account that creates intents +# USER_PK - Account that creates intents (Anvil account #3) +# On anvil1 this account starts with 10,000 ETH and is minted USDC during `make deploy`. USER_PK=3b100e493e845f3a316158400e765b3a75d92d9b922cf8e1d38b25e228a4d0a8 # ============================================================================= diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 822e13e..647c7cc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -91,8 +91,6 @@ export default function App() { const [slowLoading, setSlowLoading] = useState(false) const [slowMsg, setSlowMsg] = useState('') - const [faucetLoading, setFaucetLoading] = useState(null) - const [faucetMsg, setFaucetMsg] = useState('') const [rebalanceLoading, setRebalanceLoading] = useState(null) const [rebalanceMsg, setRebalanceMsg] = useState('') const [rebalanceToken, setRebalanceToken] = useState('USDC') @@ -225,18 +223,7 @@ export default function App() { useEffect(() => () => { if (pollRef.current) clearInterval(pollRef.current) }, []) - // ── Faucet ──────────────────────────────────────────────────────────────── - const handleFaucet = async (chainName: string, type: 'gas' | 'token', symbol?: string) => { - const key = `${chainName}-${type}${symbol ? `-${symbol}` : ''}` - setFaucetLoading(key); setFaucetMsg('') - try { - const r = await api.faucet(chainName, type, - isConnected && connectedAddress ? connectedAddress : undefined, symbol) - setFaucetMsg(`Sent ${r.amount} on ${chainName}`); loadBalances() - } catch (err: any) { setFaucetMsg(`Error: ${err.message}`) } - finally { setFaucetLoading(null) } - } // ── Rebalance ───────────────────────────────────────────────────────────── @@ -844,63 +831,6 @@ export default function App() { {rightTab === 'tools' && (
- {/* Faucet */} -
-
- Faucet - - {isConnected - ? {truncAddr(connectedAddress!)} - : config ? {truncAddr(config.userAddress)} : '—' - } - -
-

Claim testnet gas and tokens.

- {config && chainEntries.map(([chainId, chain]) => { - if (!config.faucetChains?.includes(chain.name)) return null - return ( -
-
-
- - {Object.keys(chain.tokens).map(sym => ( - - ))} -
-
- ) - })} - {faucetMsg && ( -
{faucetMsg}
- )} -
- {/* Bridge */}
Bridge diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 5ee1dee..aceb4ff 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -18,7 +18,6 @@ export interface Config { chains: Record userAddress: string solverAddress: string - faucetChains?: string[] } export interface BalanceEntry { @@ -129,13 +128,6 @@ export const api = { orderStatus: (id: string) => json(`${BASE}/order/${id}`), - faucet: (chainName: string, type: 'gas' | 'token', address?: string, symbol?: string) => - json<{ success: boolean; hash: string; amount: string }>(`${BASE}/faucet`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ chainName, type, ...(address ? { address } : {}), ...(symbol ? { symbol } : {}) }), - }), - rebalance: (from: string, to: string, amount?: string, token?: string) => json<{ success: boolean; message: string; txHash?: string }>(`${BASE}/rebalance`, { method: 'POST', From 434c5bc1eab2bda773ee0fe114fd5dc82b95b28b Mon Sep 17 00:00:00 2001 From: jonas089 Date: Tue, 10 Mar 2026 14:12:03 +0100 Subject: [PATCH 14/34] remove manual rebalancing --- frontend/src/App.tsx | 79 +------------------------------------------- frontend/src/api.ts | 7 ---- 2 files changed, 1 insertion(+), 85 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 647c7cc..3a8ee78 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -91,11 +91,6 @@ export default function App() { const [slowLoading, setSlowLoading] = useState(false) const [slowMsg, setSlowMsg] = useState('') - const [rebalanceLoading, setRebalanceLoading] = useState(null) - const [rebalanceMsg, setRebalanceMsg] = useState('') - const [rebalanceToken, setRebalanceToken] = useState('USDC') - const [rebalanceFrom, setRebalanceFrom] = useState('anvil1') - const [rebalanceTo, setRebalanceTo] = useState('anvil2') const [rightTab, setRightTab] = useState<'balances' | 'tools'>('balances') const pollRef = useRef>() @@ -225,17 +220,6 @@ export default function App() { - // ── Rebalance ───────────────────────────────────────────────────────────── - - const handleRebalance = async () => { - setRebalanceLoading('bridge'); setRebalanceMsg('') - try { - const r = await api.rebalance(rebalanceFrom, rebalanceTo, undefined, rebalanceToken) - setRebalanceMsg(r.message); loadBalances() - } catch (err: any) { setRebalanceMsg(`Error: ${err.message}`) } - finally { setRebalanceLoading(null) } - } - const resetFlowState = () => { setStep('idle'); setQuote(null); setOrderId(''); setOrderStatus(null); setError(''); setSlowMsg('') } @@ -827,71 +811,10 @@ export default function App() {
)} - {/* ── Tools tab (Faucet + Bridge + System) ────────────────────── */} + {/* ── Tools tab (System) ─────────────────────────────────────── */} {rightTab === 'tools' && (
- {/* Bridge */} -
- Bridge -

Move solver tokens via Hyperlane.

-
- -
- - - - - -
-
- - {rebalanceMsg && ( -
{rebalanceMsg}
- )} -
- {/* System */}
System diff --git a/frontend/src/api.ts b/frontend/src/api.ts index aceb4ff..c7d1035 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -128,13 +128,6 @@ export const api = { orderStatus: (id: string) => json(`${BASE}/order/${id}`), - rebalance: (from: string, to: string, amount?: string, token?: string) => - json<{ success: boolean; message: string; txHash?: string }>(`${BASE}/rebalance`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ from, to, amount, ...(token ? { token } : {}) }), - }), - bridgePrepare: (from: string, to: string, token: string, address: string, amount: string) => json<{ warpToken: string From 031e5fb636a3d664f5c793edf11f6c62ebf3f634 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Tue, 10 Mar 2026 15:06:03 +0100 Subject: [PATCH 15/34] fix anvil 1 --- hyperlane/configs/warp-config.yaml-e | 2 +- solver-cli/src/commands/chain.rs | 1 + solver-cli/src/deployment/deployer.rs | 3 +++ solver-cli/src/rebalancer/config_gen.rs | 17 ++++++++++++++++- solver-cli/src/state/types.rs | 4 ++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/hyperlane/configs/warp-config.yaml-e b/hyperlane/configs/warp-config.yaml-e index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml-e +++ b/hyperlane/configs/warp-config.yaml-e @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/solver-cli/src/commands/chain.rs b/solver-cli/src/commands/chain.rs index 8041c02..d8720a8 100644 --- a/solver-cli/src/commands/chain.rs +++ b/solver-cli/src/commands/chain.rs @@ -213,6 +213,7 @@ impl ChainCommand { let hyperlane = warp_token .as_ref() .map(|addr| crate::state::HyperlaneAddresses { + domain_id: None, mailbox: None, merkle_tree_hook: None, validator_announce: None, diff --git a/solver-cli/src/deployment/deployer.rs b/solver-cli/src/deployment/deployer.rs index a408482..9a193d5 100644 --- a/solver-cli/src/deployment/deployer.rs +++ b/solver-cli/src/deployment/deployer.rs @@ -220,6 +220,9 @@ impl Deployer { // Store Hyperlane contract addresses let hyperlane = HyperlaneAddresses { + domain_id: chain_data + .get("domain_id") + .and_then(|v| v.as_u64()), mailbox: chain_data .get("mailbox") .and_then(|v| v.as_str()) diff --git a/solver-cli/src/rebalancer/config_gen.rs b/solver-cli/src/rebalancer/config_gen.rs index b6e36c1..35b641f 100644 --- a/solver-cli/src/rebalancer/config_gen.rs +++ b/solver-cli/src/rebalancer/config_gen.rs @@ -55,7 +55,7 @@ impl RebalancerConfigGenerator { [[chains]] name = "{name}" chain_id = {chain_id} -domain_id = {chain_id} +domain_id = {domain_id} rpc_url = "{rpc_url}" account = "{account}" [chains.signer] @@ -63,6 +63,9 @@ account = "{account}" "#, name = chain.name, chain_id = chain.chain_id, + domain_id = chain.contracts.hyperlane.as_ref() + .and_then(|h| h.domain_id) + .unwrap_or_else(|| hyperlane_domain_id(chain.chain_id)), rpc_url = chain.rpc, account = account, signer_inline = signer_inline, @@ -276,6 +279,16 @@ fn derive_rebalancer_account(state: &SolverState) -> Result { ) } +/// Map EVM chain ID to Hyperlane domain ID. +/// Domain IDs can differ from chain IDs to avoid conflicts with Hyperlane's +/// hardcoded KnownHyperlaneDomain enum (e.g. 31337 is hardcoded as "test4"). +fn hyperlane_domain_id(chain_id: u64) -> u64 { + match chain_id { + 31337 => 131337, + _ => chain_id, + } +} + fn normalize_address(value: &str) -> Result { let address: Address = value.parse()?; Ok(format!("{:?}", address)) @@ -313,6 +326,7 @@ mod tests { oracle: Some("0x0000000000000000000000000000000000000103".to_string()), permit2: Some("0x0000000000000000000000000000000000000104".to_string()), hyperlane: Some(HyperlaneAddresses { + domain_id: None, mailbox: None, merkle_tree_hook: None, validator_announce: None, @@ -347,6 +361,7 @@ mod tests { oracle: Some("0x0000000000000000000000000000000000000203".to_string()), permit2: Some("0x0000000000000000000000000000000000000204".to_string()), hyperlane: Some(HyperlaneAddresses { + domain_id: None, mailbox: None, merkle_tree_hook: None, validator_announce: None, diff --git a/solver-cli/src/state/types.rs b/solver-cli/src/state/types.rs index dd2d81f..e373d2b 100644 --- a/solver-cli/src/state/types.rs +++ b/solver-cli/src/state/types.rs @@ -106,6 +106,10 @@ pub struct ContractAddresses { #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct HyperlaneAddresses { + /// Hyperlane domain ID (may differ from EVM chain ID) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub domain_id: Option, + /// Hyperlane mailbox address pub mailbox: Option, From 919e98018e9a2d2f2dcab8e63ebff9655a819791 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Tue, 10 Mar 2026 15:07:31 +0100 Subject: [PATCH 16/34] format --- hyperlane/configs/warp-config.yaml-e | 2 +- solver-cli/src/deployment/deployer.rs | 4 +--- solver-cli/src/rebalancer/config_gen.rs | 5 ++++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/hyperlane/configs/warp-config.yaml-e b/hyperlane/configs/warp-config.yaml-e index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml-e +++ b/hyperlane/configs/warp-config.yaml-e @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/solver-cli/src/deployment/deployer.rs b/solver-cli/src/deployment/deployer.rs index 9a193d5..b48083e 100644 --- a/solver-cli/src/deployment/deployer.rs +++ b/solver-cli/src/deployment/deployer.rs @@ -220,9 +220,7 @@ impl Deployer { // Store Hyperlane contract addresses let hyperlane = HyperlaneAddresses { - domain_id: chain_data - .get("domain_id") - .and_then(|v| v.as_u64()), + domain_id: chain_data.get("domain_id").and_then(|v| v.as_u64()), mailbox: chain_data .get("mailbox") .and_then(|v| v.as_str()) diff --git a/solver-cli/src/rebalancer/config_gen.rs b/solver-cli/src/rebalancer/config_gen.rs index 35b641f..fdef4ee 100644 --- a/solver-cli/src/rebalancer/config_gen.rs +++ b/solver-cli/src/rebalancer/config_gen.rs @@ -63,7 +63,10 @@ account = "{account}" "#, name = chain.name, chain_id = chain.chain_id, - domain_id = chain.contracts.hyperlane.as_ref() + domain_id = chain + .contracts + .hyperlane + .as_ref() .and_then(|h| h.domain_id) .unwrap_or_else(|| hyperlane_domain_id(chain.chain_id)), rpc_url = chain.rpc, From c11b13b605789374682d86d68b183b04b1e2c357 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Tue, 10 Mar 2026 15:31:37 +0100 Subject: [PATCH 17/34] minor fix --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5e2b332..f94ac15 100644 --- a/Makefile +++ b/Makefile @@ -214,7 +214,7 @@ fund-user: ## mint: Mint tokens on a chain (SYMBOL=USDC, CHAIN=anvil1, TO=user, AMOUNT=10000000) mint: build - @$(SOLVER_CLI) token mint \ + @unset CHAIN; $(SOLVER_CLI) token mint \ --chain $(or $(CHAIN),anvil1) \ --symbol $(or $(SYMBOL),USDC) \ --to $(or $(TO),user) \ From c5464d179d1a438125fa8a134fe0d466df78af9f Mon Sep 17 00:00:00 2001 From: jonas089 Date: Wed, 11 Mar 2026 12:49:56 +0100 Subject: [PATCH 18/34] remove permit2 --- .env.example | 3 - Makefile | 17 ++- frontend/server.js | 119 ------------------ hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 +++++ hyperlane/hyperlane-cosmosnative.json | 8 ++ .../registry/chains/anvil1/addresses.yaml | 14 +++ .../registry/chains/anvil2/addresses.yaml | 14 +++ .../warp_routes/USDC/warp-config-config.yaml | 19 +++ .../warp_routes/USDC/warp-config-deploy.yaml | 15 +++ hyperlane/scripts/docker-entrypoint.sh | 6 +- solver-cli/src/commands/chain.rs | 1 - solver-cli/src/deployment/deployer.rs | 1 - solver-cli/src/deployment/forge.rs | 4 - solver-cli/src/rebalancer/config_gen.rs | 2 - solver-cli/src/solver/config_gen.rs | 2 - solver-cli/src/state/types.rs | 4 - 17 files changed, 119 insertions(+), 141 deletions(-) create mode 100644 hyperlane/hyperlane-addresses.json create mode 100644 hyperlane/hyperlane-cosmosnative.json create mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml create mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/.env.example b/.env.example index 9c82bf2..ed0a2da 100644 --- a/.env.example +++ b/.env.example @@ -38,9 +38,6 @@ ORACLE_OPERATOR_PK=redacted #ORACLE_KMS_KEY_ID= #ORACLE_KMS_REGION=us-east-1 -# Bridge signer — hot wallet key for the frontend bridge tool (Node.js cannot use KMS directly) -BRIDGE_SIGNER_PK=redacted - # Rebalancer signer — choose one: # Option A: local key (or per-chain REBALANCER__PK) REBALANCER_PRIVATE_KEY=redacted diff --git a/Makefile b/Makefile index f94ac15..817957d 100644 --- a/Makefile +++ b/Makefile @@ -221,6 +221,21 @@ mint: build --amount $(or $(AMOUNT),10000000) .PHONY: mint +## fund-address: Fund any address with ETH and USDC on anvil1 +## Usage: make fund-address ADDR=0x... ETH=10 USDC=100000000 +fund-address: + @[ -n "$(ADDR)" ] || (echo "Error: ADDR is required. Usage: make fund-address ADDR=0x..."; exit 1) + @. ./.env && \ + echo "Funding $(ADDR) on anvil1..." && \ + cast send --rpc-url $$ANVIL1_RPC --private-key $$ANVIL1_PK --value $(or $(ETH),10)ether $(ADDR) && \ + echo " Sent $(or $(ETH),10) ETH" && \ + cast send --rpc-url $$ANVIL1_RPC --private-key $$ANVIL1_PK \ + $$(cat .config/state.json | python3 -c "import sys,json; chains=json.load(sys.stdin)['chains']; print(next(c['tokens']['USDC']['address'] for c in chains.values() if c['name']=='anvil1'))") \ + "mint(address,uint256)" $(ADDR) $(or $(USDC),100000000) && \ + echo " Minted $(or $(USDC),100000000) USDC (raw units)" && \ + echo "Done." +.PHONY: fund-address + # ============================================================================ # Services # ============================================================================ @@ -239,7 +254,7 @@ solver: solver-start ## operator-start: Start the oracle operator service operator-start: - @cd oracle-operator && ORACLE_CONFIG=../.config/oracle.toml RUST_LOG=info cargo run --release + @set -a && . ./.env && set +a && cd oracle-operator && ORACLE_CONFIG=../.config/oracle.toml RUST_LOG=info cargo run --release .PHONY: operator-start # Alias for convenience diff --git a/frontend/server.js b/frontend/server.js index 3afc83f..24110db 100644 --- a/frontend/server.js +++ b/frontend/server.js @@ -442,125 +442,6 @@ app.post('/api/faucet', async (req, res) => { } }); -// Rebalance: bridge tokens between any two chains via Celestia forwarding -app.post('/api/rebalance', async (req, res) => { - const { from, to, amount = '10000000', token = 'USDC' } = req.body; - if (!from || !to) return res.status(400).json({ error: '"from" and "to" chain names are required' }); - try { - // 1. Load warp route config for this token - const warp = getWarpRouteConfig(token); - const src = warp.chains[from]; - const dst = warp.chains[to]; - if (!src) throw new Error(`Chain "${from}" not found in ${token} warp route. Have you enrolled the routers?`); - if (!dst) throw new Error(`Chain "${to}" not found in ${token} warp route. Have you enrolled the routers?`); - - // Only the collateral side needs an ERC20 approve before transferRemote - const underlyingToApprove = src.warpType === 'collateral' ? src.underlying : null; - // On the collateral side, received tokens land as the underlying ERC20 (not the warp token) - const dstBalanceToken = dst.warpType === 'collateral' ? dst.underlying : dst.warpToken; - - // 2. Setup viem clients - // BRIDGE_SIGNER_PK is a hot wallet key for the frontend bridge tool. - // Falls back to REBALANCER_PRIVATE_KEY, then SOLVER_PRIVATE_KEY. - // Required when solver/rebalancer use AWS KMS (which Node.js cannot sign with directly). - const solverPk = process.env.BRIDGE_SIGNER_PK || process.env.REBALANCER_PRIVATE_KEY || process.env.SOLVER_PRIVATE_KEY; - if (!solverPk) throw new Error('No bridge signer key found. Set BRIDGE_SIGNER_PK (or REBALANCER_PRIVATE_KEY / SOLVER_PRIVATE_KEY) in .env'); - const solver = privateKeyToAccount(`0x${solverPk.replace('0x', '')}`); - - const srcChain = makeViemChain(src.chainId, from, src.rpc); - const dstChain = makeViemChain(dst.chainId, to, dst.rpc); - - const srcWallet = createWalletClient({ account: solver, chain: srcChain, transport: http(src.rpc) }); - const srcPublic = createPublicClient({ chain: srcChain, transport: http(src.rpc) }); - const dstPublic = createPublicClient({ chain: dstChain, transport: http(dst.rpc) }); - - const solverPadded = '0x000000000000000000000000' + solver.address.slice(2); - const amountBigInt = BigInt(amount); - - console.log(`[rebalance] ${token} ${from}→${to} amount=${amount}`); - console.log(`[rebalance] solver=${solver.address} srcWarp=${src.warpToken} dstBalance=${dstBalanceToken}`); - - // 3. Check initial balance on destination - const initialDstBal = await dstPublic.readContract({ - address: dstBalanceToken, abi: hypTokenAbi, - functionName: 'balanceOf', args: [solver.address], - }); - console.log(`[rebalance] Initial dst balance: ${initialDstBal}`); - - // 4. Derive Celestia forwarding address - const forwardingBackend = process.env.FORWARDING_BACKEND || 'http://127.0.0.1:8080'; - const addrResp = await fetch(`${forwardingBackend}/forwarding-address?dest_domain=${dst.domainId}&dest_recipient=${solverPadded}`); - if (!addrResp.ok) { - const body = await addrResp.text(); - throw new Error(`Failed to derive forwarding address: ${body}`); - } - const { address: forwardAddr } = await addrResp.json(); - console.log(`[rebalance] Forwarding address: ${forwardAddr}`); - - // 5. Register forwarding request with backend - const registerResp = await fetch(`${forwardingBackend}/forwarding-requests`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - forward_addr: forwardAddr, - dest_domain: dst.domainId, - dest_recipient: solverPadded, - }), - }); - if (!registerResp.ok) { - const body = await registerResp.text(); - throw new Error(`Forwarding registration failed: ${body}`); - } - console.log(`[rebalance] Forwarding registered`); - - // 6. Approve underlying ERC20 → HypCollateral (only for collateral source) - if (underlyingToApprove) { - const approveHash = await srcWallet.writeContract({ - address: underlyingToApprove, abi: erc20Abi, - functionName: 'approve', args: [src.warpToken, amountBigInt], - }); - await srcPublic.waitForTransactionReceipt({ hash: approveHash }); - console.log(`[rebalance] Approved ${amount} of ${underlyingToApprove} → ${src.warpToken}`); - } - - // 7. Call transferRemote on the warp token - const forwardAddrBytes32 = bech32ToBytes32(forwardAddr); - const txHash = await srcWallet.writeContract({ - address: src.warpToken, abi: hypTokenAbi, - functionName: 'transferRemote', - args: [warp.celestiaDomainId, forwardAddrBytes32, amountBigInt], - value: 0n, - }); - await srcPublic.waitForTransactionReceipt({ hash: txHash }); - console.log(`[rebalance] transferRemote sent: ${txHash}`); - - // 8. Poll destination balance (up to 60s) - let arrived = false; - for (let i = 1; i <= 12; i++) { - await new Promise(r => setTimeout(r, 5000)); - const bal = await dstPublic.readContract({ - address: dstBalanceToken, abi: hypTokenAbi, - functionName: 'balanceOf', args: [solver.address], - }); - console.log(`[rebalance] [${i * 5}s] dst balance: ${bal}`); - if (bal !== initialDstBal && bal > 0n) { arrived = true; break; } - } - - if (arrived) { - res.json({ success: true, message: `Bridged ${token} from ${from} to ${to}`, txHash }); - } else { - res.json({ - success: false, - message: `Bridge tx sent (${txHash}) but tokens haven't arrived on ${to} yet. Check Hyperlane relayer logs.`, - txHash, - }); - } - } catch (err) { - console.error(`[rebalance] Failed:`, err.message); - res.status(500).json({ error: `Rebalance failed: ${err.message}` }); - } -}); - // Bridge prepare: derive forwarding address + register, return contract info for wallet-side execution app.post('/api/bridge/prepare', async (req, res) => { const { from, to, token = 'USDC', amount = '10000000', address } = req.body; diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json new file mode 100644 index 0000000..64f24e3 --- /dev/null +++ b/hyperlane/hyperlane-addresses.json @@ -0,0 +1,29 @@ +{ + "anvil1": { + "chain_id": 31337, + "domain_id": 131337, + "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "warp_token_type": "collateral", + "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "anvil2": { + "chain_id": 31338, + "domain_id": 31338, + "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "warp_token_type": "synthetic", + "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + }, + "celestiadev": { + "domain_id": 69420, + "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" + } +} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json new file mode 100644 index 0000000..ae2a37b --- /dev/null +++ b/hyperlane/hyperlane-cosmosnative.json @@ -0,0 +1,8 @@ +{ + "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", + "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", + "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" +} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml new file mode 100644 index 0000000..86b298a --- /dev/null +++ b/hyperlane/registry/chains/anvil1/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" +merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" +validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml new file mode 100644 index 0000000..ad2a018 --- /dev/null +++ b/hyperlane/registry/chains/anvil2/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" +proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml new file mode 100644 index 0000000..4ad5a1b --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../schema.json +tokens: + - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + chainName: anvil1 + collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + connections: + - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 + decimals: 6 + name: USDC + standard: EvmHypCollateral + symbol: USDC + - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + chainName: anvil2 + connections: + - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 + decimals: 6 + name: USDC + standard: EvmHypSynthetic + symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml new file mode 100644 index 0000000..dec3a6a --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml @@ -0,0 +1,15 @@ +anvil1: + decimals: 6 + mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + type: collateral +anvil2: + decimals: 6 + mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + type: synthetic diff --git a/hyperlane/scripts/docker-entrypoint.sh b/hyperlane/scripts/docker-entrypoint.sh index 1ecdab7..c36d363 100755 --- a/hyperlane/scripts/docker-entrypoint.sh +++ b/hyperlane/scripts/docker-entrypoint.sh @@ -41,15 +41,15 @@ echo "$MOCK_USDC_OUTPUT" MOCK_USDC_ADDR=$(echo "$MOCK_USDC_OUTPUT" | grep "Deployed to:" | awk '{print $3}') echo "MockERC20 USDC deployed on anvil1: $MOCK_USDC_ADDR" -# Mint initial supply to deployer (100M USDC = 100000000 * 10^6) +# Mint initial supply to deployer (100 USDC = 100 * 10^6) cast send $MOCK_USDC_ADDR \ "mint(address,uint256)" \ 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \ - 100000000000000 \ + 100000000 \ --rpc-url $ANVIL1_RPC_URL \ --private-key $HYP_KEY -echo "Minted 100M USDC to deployer on anvil1" +echo "Minted 100 USDC to deployer on anvil1" # ============================================================================ # Step 2: Deploy Hyperlane core contracts to both EVM chains diff --git a/solver-cli/src/commands/chain.rs b/solver-cli/src/commands/chain.rs index d8720a8..d213755 100644 --- a/solver-cli/src/commands/chain.rs +++ b/solver-cli/src/commands/chain.rs @@ -226,7 +226,6 @@ impl ChainCommand { input_settler_escrow: Some(input_settler.clone()), output_settler_simple: Some(output_settler.clone()), oracle: Some(oracle.clone()), - permit2: None, hyperlane, }; diff --git a/solver-cli/src/deployment/deployer.rs b/solver-cli/src/deployment/deployer.rs index b48083e..de46dac 100644 --- a/solver-cli/src/deployment/deployer.rs +++ b/solver-cli/src/deployment/deployer.rs @@ -52,7 +52,6 @@ impl Deployer { input_settler_escrow: deployment.input_settler().cloned(), output_settler_simple: deployment.output_settler().cloned(), oracle: deployment.oracle().cloned(), - permit2: deployment.permit2().cloned(), hyperlane: None, }; diff --git a/solver-cli/src/deployment/forge.rs b/solver-cli/src/deployment/forge.rs index d1ba8f1..d5bd3a4 100644 --- a/solver-cli/src/deployment/forge.rs +++ b/solver-cli/src/deployment/forge.rs @@ -224,8 +224,4 @@ impl DeploymentOutput { .get("OutputSettlerSimple") .or_else(|| self.addresses.get("OutputSettler")) } - - pub fn permit2(&self) -> Option<&String> { - self.addresses.get("Permit2") - } } diff --git a/solver-cli/src/rebalancer/config_gen.rs b/solver-cli/src/rebalancer/config_gen.rs index fdef4ee..f16f068 100644 --- a/solver-cli/src/rebalancer/config_gen.rs +++ b/solver-cli/src/rebalancer/config_gen.rs @@ -327,7 +327,6 @@ mod tests { "0x0000000000000000000000000000000000000102".to_string(), ), oracle: Some("0x0000000000000000000000000000000000000103".to_string()), - permit2: Some("0x0000000000000000000000000000000000000104".to_string()), hyperlane: Some(HyperlaneAddresses { domain_id: None, mailbox: None, @@ -362,7 +361,6 @@ mod tests { "0x0000000000000000000000000000000000000202".to_string(), ), oracle: Some("0x0000000000000000000000000000000000000203".to_string()), - permit2: Some("0x0000000000000000000000000000000000000204".to_string()), hyperlane: Some(HyperlaneAddresses { domain_id: None, mailbox: None, diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index 851f0e2..c6c47f4 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -59,7 +59,6 @@ impl ConfigGenerator { [networks.{}] input_settler_address = "{}" output_settler_address = "{}" -permit2_address = "{}" [[networks.{}.rpc_urls]] http = "{}" @@ -75,7 +74,6 @@ http = "{}" .output_settler_simple .as_deref() .unwrap_or(""), - chain.contracts.permit2.as_deref().unwrap_or(""), chain.chain_id, chain.rpc, )); diff --git a/solver-cli/src/state/types.rs b/solver-cli/src/state/types.rs index e373d2b..b758456 100644 --- a/solver-cli/src/state/types.rs +++ b/solver-cli/src/state/types.rs @@ -96,9 +96,6 @@ pub struct ContractAddresses { /// Oracle contract pub oracle: Option, - /// Permit2 contract - pub permit2: Option, - /// Hyperlane addresses (if deployed via Hyperlane warp route) #[serde(default, skip_serializing_if = "Option::is_none")] pub hyperlane: Option, @@ -134,7 +131,6 @@ impl ContractAddresses { self.input_settler_escrow.is_some() && self.output_settler_simple.is_some() && self.oracle.is_some() - && self.permit2.is_some() } } From 88c336b4d2c894ca3ceaf7f56533c41b4b40175c Mon Sep 17 00:00:00 2001 From: jonas089 Date: Wed, 11 Mar 2026 13:17:23 +0100 Subject: [PATCH 19/34] cleanup --- docs/add-chain-sepolia.md | 4 --- hyperlane/configs/warp-config-sepolia.yaml | 6 ++++ hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 ------------------- hyperlane/hyperlane-cosmosnative.json | 8 ----- .../registry/chains/anvil1/addresses.yaml | 14 --------- .../registry/chains/anvil2/addresses.yaml | 14 --------- .../registry/chains/sepolia/addresses.yaml | 27 ++++++++--------- .../registry/chains/sepolia/metadata.yaml | 2 +- .../warp_routes/USDC/warp-config-config.yaml | 19 ------------ .../warp_routes/USDC/warp-config-deploy.yaml | 15 ---------- hyperlane/relayer-config.json | 4 +-- 12 files changed, 24 insertions(+), 120 deletions(-) create mode 100644 hyperlane/configs/warp-config-sepolia.yaml delete mode 100644 hyperlane/hyperlane-addresses.json delete mode 100644 hyperlane/hyperlane-cosmosnative.json delete mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml delete mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/docs/add-chain-sepolia.md b/docs/add-chain-sepolia.md index d5df243..ca26dd0 100644 --- a/docs/add-chain-sepolia.md +++ b/docs/add-chain-sepolia.md @@ -162,7 +162,6 @@ export CEL_TOKEN=0x... # paste from above **Enroll Celestia on Sepolia** — tells Sepolia's bridge contract that Celestia (domain 69420) is a valid route: ```bash -. ./.env cast send $HYP_SYNTHETIC_SEPOLIA \ "enrollRemoteRouter(uint32,bytes32)" \ 69420 $CEL_TOKEN \ @@ -202,8 +201,6 @@ anvil1 (HypCollateral) ↔ Celestia (native synthetic) ↔ anvil2 (HypSynthetic) The relayer passes messages between chains. Add Sepolia to `hyperlane/relayer-config.json`: ```bash -. ./.env - # Read the Sepolia mailbox address from the registry file downloaded in Step 3 SEPOLIA_MAILBOX=$(grep "^mailbox:" hyperlane/registry/chains/sepolia/addresses.yaml | awk '{print $2}' | tr -d '"') echo "Sepolia mailbox: $SEPOLIA_MAILBOX" @@ -261,7 +258,6 @@ make token-list CHAIN=sepolia **Solver** — needs ETH on Sepolia to pay gas when filling orders there: ```bash -. ./.env SOLVER_ADDR=$(cast wallet address --private-key $SOLVER_PRIVATE_KEY) echo "Funding solver: $SOLVER_ADDR" diff --git a/hyperlane/configs/warp-config-sepolia.yaml b/hyperlane/configs/warp-config-sepolia.yaml new file mode 100644 index 0000000..dcfee62 --- /dev/null +++ b/hyperlane/configs/warp-config-sepolia.yaml @@ -0,0 +1,6 @@ +sepolia: + type: synthetic + owner: "0x9f2CD91d150236BA9796124F3Dcda305C3a2086C" + name: "USDC" + symbol: "USDC" + decimals: 6 diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json deleted file mode 100644 index 64f24e3..0000000 --- a/hyperlane/hyperlane-addresses.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "anvil1": { - "chain_id": 31337, - "domain_id": 131337, - "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", - "warp_token_type": "collateral", - "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" - }, - "anvil2": { - "chain_id": 31338, - "domain_id": 31338, - "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "warp_token_type": "synthetic", - "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" - }, - "celestiadev": { - "domain_id": 69420, - "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", - "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" - } -} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json deleted file mode 100644 index ae2a37b..0000000 --- a/hyperlane/hyperlane-cosmosnative.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", - "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", - "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" -} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml deleted file mode 100644 index 86b298a..0000000 --- a/hyperlane/registry/chains/anvil1/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" -merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" -testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" -validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml deleted file mode 100644 index ad2a018..0000000 --- a/hyperlane/registry/chains/anvil2/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" -proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" -staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/chains/sepolia/addresses.yaml b/hyperlane/registry/chains/sepolia/addresses.yaml index 9bca1de..3ee8f78 100644 --- a/hyperlane/registry/chains/sepolia/addresses.yaml +++ b/hyperlane/registry/chains/sepolia/addresses.yaml @@ -1,13 +1,14 @@ -domainRoutingIsmFactory: "0x8eBee8e6596Ae0Bd8A301D2a4163B1387Fc2E5F4" -interchainAccountRouter: "0x168fda89d2059cC33a822C73b2e7386356209C21" -mailbox: "0xF12598dd5A9aec25AA9355382eC884117f3093Cb" -merkleTreeHook: "0x8D8dC02315B6fDe3A81F0D333d673D4E8383eaC9" -proxyAdmin: "0x3F037d70f5a4e5e3E1e88fB6354e50a5eA158a92" -staticAggregationHookFactory: "0x3710181b357350725a5208a5358d0c835600fd2b" -staticAggregationIsmFactory: "0xe1f66ee601DE5e39220744aA3Bda9c0343D29B1A" -staticMerkleRootMultisigIsmFactory: "0x1048693487Ea12Bd969FBFDC53416aFA57ba9C49" -staticMerkleRootWeightedMultisigIsmFactory: "0x76E04CFaBC773512c72Ef056d36eBB1971A6028B" -staticMessageIdMultisigIsmFactory: "0xA25C642828F051F53fb4d1E52d3E6cc563a1a6F7" -staticMessageIdWeightedMultisigIsmFactory: "0x781e51D1Aa29429d981840357faB1f9Df8Db6cBb" -testRecipient: "0x78daf39c666a0b9F006DaE0D3724371B59E8Cc88" -validatorAnnounce: "0x575CE87d8cb90CCEf0172bB6Ec644D4567247312" +domainRoutingIsmFactory: "0x6556Aae6eb48C0DDCf6537490eD693EA43F42ef0" +incrementalDomainRoutingIsmFactory: "0x934D19F13ec8D025060Dc6017f6A3CF74c05E8D9" +interchainAccountRouter: "0x3f12B3977A0000cE60EC5BfEEF47379Df47a4627" +mailbox: "0x409c1D7f3bDC4e30E4502DeD20D8Ad561D60606c" +merkleTreeHook: "0xDfeD4337E52346a1e7722610648A6eB54cAB86ca" +proxyAdmin: "0xEDADdd77530E5D26FEd82082967E7011fC304456" +staticAggregationHookFactory: "0xe95d66048B614709CCD4c4A36c9855c156ECa1Bf" +staticAggregationIsmFactory: "0x6b7f78bD7895d1773F8c34489821f5fd04d32B21" +staticMerkleRootMultisigIsmFactory: "0xb87DF876d2E2C2B1CE38Ca9e7f1854f370d8d7D3" +staticMerkleRootWeightedMultisigIsmFactory: "0x3Bc8F11585D5E265576fE7e9269E17C8CA765a0B" +staticMessageIdMultisigIsmFactory: "0x86417252951a2E8e200f036C82EA79719a08d85e" +staticMessageIdWeightedMultisigIsmFactory: "0x97ab3Bd14dD519f3EAB52BEE11fb0A20A3939699" +testRecipient: "0xC250C9F3C03c20C72264631D4437ba14Edd7d3F7" +validatorAnnounce: "0x7286d8dA68Cdbd7c0fFC3E4E4AAC16b904eB6c45" diff --git a/hyperlane/registry/chains/sepolia/metadata.yaml b/hyperlane/registry/chains/sepolia/metadata.yaml index ffdeeb0..adb4a5c 100644 --- a/hyperlane/registry/chains/sepolia/metadata.yaml +++ b/hyperlane/registry/chains/sepolia/metadata.yaml @@ -9,5 +9,5 @@ nativeToken: symbol: ETH protocol: ethereum rpcUrls: - - http: https://rpc.ankr.com/eth_sepolia/3afc3fea39333420bb3a7b29067d4291b79d761cd18faafec716442cd0be0be2 + - http: https://ethereum-sepolia-rpc.publicnode.com technicalStack: other diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml deleted file mode 100644 index 4ad5a1b..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# yaml-language-server: $schema=../schema.json -tokens: - - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" - chainName: anvil1 - collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - connections: - - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 - decimals: 6 - name: USDC - standard: EvmHypCollateral - symbol: USDC - - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" - chainName: anvil2 - connections: - - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 - decimals: 6 - name: USDC - standard: EvmHypSynthetic - symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml deleted file mode 100644 index dec3a6a..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml +++ /dev/null @@ -1,15 +0,0 @@ -anvil1: - decimals: 6 - mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - type: collateral -anvil2: - decimals: 6 - mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - type: synthetic diff --git a/hyperlane/relayer-config.json b/hyperlane/relayer-config.json index 372abbf..661ef1c 100644 --- a/hyperlane/relayer-config.json +++ b/hyperlane/relayer-config.json @@ -141,14 +141,14 @@ "protocol": "ethereum", "rpcUrls": [ { - "http": "https://rpc.ankr.com/eth_sepolia/3afc3fea39333420bb3a7b29067d4291b79d761cd18faafec716442cd0be0be2" + "http": "https://ethereum-sepolia-rpc.publicnode.com" } ], "signer": { "type": "hexKey", "key": "0x52d441beb407f47811a09ed9d330320b2d336482512f26e9a5c5d3dacddc7b1e" }, - "mailbox": "0xF12598dd5A9aec25AA9355382eC884117f3093Cb" + "mailbox": "0x409c1D7f3bDC4e30E4502DeD20D8Ad561D60606c" } }, "defaultRpcConsensusType": "fallback", From 6f85e9324d4c32fb2381d4eef3a85d410d98661d Mon Sep 17 00:00:00 2001 From: jonas089 Date: Wed, 11 Mar 2026 15:06:25 +0100 Subject: [PATCH 20/34] wip --- Makefile | 200 +++++------------------ scripts/systemd/oif-aggregator.service | 18 ++ scripts/systemd/oif-frontend-api.service | 17 ++ scripts/systemd/oif-frontend.service | 17 ++ scripts/systemd/oif-oracle.service | 19 +++ scripts/systemd/oif-rebalancer.service | 17 ++ scripts/systemd/oif-solver.service | 17 ++ scripts/systemd/oif.target | 6 + solver-cli/src/commands/configure.rs | 35 +++- 9 files changed, 185 insertions(+), 161 deletions(-) create mode 100644 scripts/systemd/oif-aggregator.service create mode 100644 scripts/systemd/oif-frontend-api.service create mode 100644 scripts/systemd/oif-frontend.service create mode 100644 scripts/systemd/oif-oracle.service create mode 100644 scripts/systemd/oif-rebalancer.service create mode 100644 scripts/systemd/oif-solver.service create mode 100644 scripts/systemd/oif.target diff --git a/Makefile b/Makefile index 817957d..d0b7d3b 100644 --- a/Makefile +++ b/Makefile @@ -306,166 +306,6 @@ balances: build @$(SOLVER_CLI) balances $(if $(CHAIN),--chain $(CHAIN),) .PHONY: balances -# ============================================================================ -# Bridge Test (Hyperlane warp route e2e) -# ============================================================================ - -## rebalance: Move solver USDC from anvil1 -> Celestia -> anvil2 via Hyperlane + forwarding -FORWARDING_BACKEND ?= http://127.0.0.1:8080 -REBALANCE_AMOUNT ?= 10000000 - -rebalance: - @echo "" - @echo "═══════════════════════════════════════════════════════════════" - @echo " Rebalance: anvil1 -> Celestia -> anvil2" - @echo "═══════════════════════════════════════════════════════════════" - @echo "" - @. ./.env && \ - ADDRESSES=$$(cat .config/hyperlane-addresses.json) && \ - MOCK_USDC=$$(echo $$ADDRESSES | jq -r '.anvil1.mock_usdc') && \ - ANVIL1_WARP=$$(echo $$ADDRESSES | jq -r '.anvil1.warp_token') && \ - ANVIL2_WARP=$$(echo $$ADDRESSES | jq -r '.anvil2.warp_token') && \ - SOLVER_ADDR=$$(cast wallet address --private-key $$SOLVER_PRIVATE_KEY) && \ - SOLVER_ADDR_PADDED=$$(printf '0x000000000000000000000000%s' $${SOLVER_ADDR#0x}) && \ - echo " Solver address: $$SOLVER_ADDR" && \ - echo " MockERC20 (anvil1): $$MOCK_USDC" && \ - echo " HypCollateral (anvil1): $$ANVIL1_WARP" && \ - echo " HypSynthetic (anvil2): $$ANVIL2_WARP" && \ - echo " Amount: $(REBALANCE_AMOUNT) (raw)" && \ - echo "" && \ - echo "Step 1: Check initial balances..." && \ - ANVIL1_BAL=$$(cast call $$MOCK_USDC "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL1_RPC 2>/dev/null | cast to-dec 2>/dev/null || echo "0") && \ - ANVIL2_BAL=$$(cast call $$ANVIL2_WARP "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL2_RPC 2>/dev/null | cast to-dec 2>/dev/null || echo "0") && \ - echo " Anvil1 USDC: $$ANVIL1_BAL" && \ - echo " Anvil2 USDC: $$ANVIL2_BAL" && \ - echo "" && \ - echo "Step 2: Derive Celestia forwarding address (dest: anvil2)..." && \ - FORWARD_ADDR=$$(docker exec forwarding-relayer forwarding-relayer derive-address \ - --dest-domain 31338 \ - --dest-recipient $$SOLVER_ADDR_PADDED) && \ - echo " Forwarding address: $$FORWARD_ADDR" && \ - echo "" && \ - echo "Step 3: Register forwarding request with backend..." && \ - REGISTER_RESP=$$(curl -sf -X POST $(FORWARDING_BACKEND)/forwarding-requests \ - -H "Content-Type: application/json" \ - -d "{\"forward_addr\": \"$$FORWARD_ADDR\", \"dest_domain\": 31338, \"dest_recipient\": \"$$SOLVER_ADDR_PADDED\"}") && \ - echo " Response: $$REGISTER_RESP" && \ - echo "" && \ - echo "Step 4: Approve HypCollateral to spend solver USDC..." && \ - cast send $$MOCK_USDC "approve(address,uint256)" $$ANVIL1_WARP $(REBALANCE_AMOUNT) \ - --rpc-url $$ANVIL1_RPC --private-key $$SOLVER_PRIVATE_KEY > /dev/null && \ - echo " Done" && \ - echo "" && \ - echo "Step 5: Send USDC to Celestia via HypCollateral.transferRemote(69420, ...)..." && \ - FORWARD_ADDR_HEX=$$(python3 scripts/bech32_to_bytes32.py "$$FORWARD_ADDR") && \ - echo " Forwarding addr (bytes32): $$FORWARD_ADDR_HEX" && \ - cast send $$ANVIL1_WARP "transferRemote(uint32,bytes32,uint256)" \ - 69420 $$FORWARD_ADDR_HEX $(REBALANCE_AMOUNT) \ - --rpc-url $$ANVIL1_RPC --private-key $$SOLVER_PRIVATE_KEY --value 0 > /dev/null && \ - echo " Sent! anvil1 -> Celestia -> anvil2" && \ - echo "" && \ - echo "Step 6: Waiting for Hyperlane relayer + Celestia forwarding (60s)..." && \ - for i in $$(seq 1 12); do \ - sleep 5; \ - BAL=$$(cast call $$ANVIL2_WARP "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL2_RPC 2>/dev/null | cast to-dec 2>/dev/null || echo "0"); \ - printf " [%2ds] Anvil2 USDC balance: %s\n" $$((i * 5)) "$$BAL"; \ - if [ "$$BAL" != "$$ANVIL2_BAL" ] && [ "$$BAL" != "0" ]; then \ - echo ""; \ - echo " Tokens arrived!"; \ - break; \ - fi; \ - done && \ - echo "" && \ - echo "Step 7: Final balances..." && \ - ANVIL1_BAL_AFTER=$$(cast call $$MOCK_USDC "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL1_RPC | cast to-dec) && \ - ANVIL2_BAL_AFTER=$$(cast call $$ANVIL2_WARP "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL2_RPC | cast to-dec) && \ - echo " Anvil1 USDC: $$ANVIL1_BAL -> $$ANVIL1_BAL_AFTER" && \ - echo " Anvil2 USDC: $$ANVIL2_BAL -> $$ANVIL2_BAL_AFTER" && \ - echo "" && \ - if [ "$$ANVIL2_BAL_AFTER" != "$$ANVIL2_BAL" ]; then \ - echo " Rebalance complete — tokens moved anvil1 -> Celestia -> anvil2"; \ - else \ - echo " Rebalance failed — tokens did not arrive on anvil2"; \ - echo " Check logs: make logs SVC=relayer"; \ - echo " make logs SVC=forwarding-relayer"; \ - fi && \ - echo "" -.PHONY: rebalance - -## rebalance-back: Move solver USDC from anvil2 -> Celestia -> anvil1 via Hyperlane + forwarding -rebalance-back: - @echo "" - @echo "═══════════════════════════════════════════════════════════════" - @echo " Rebalance Back: anvil2 -> Celestia -> anvil1" - @echo "═══════════════════════════════════════════════════════════════" - @echo "" - @. ./.env && \ - ADDRESSES=$$(cat .config/hyperlane-addresses.json) && \ - MOCK_USDC=$$(echo $$ADDRESSES | jq -r '.anvil1.mock_usdc') && \ - ANVIL1_WARP=$$(echo $$ADDRESSES | jq -r '.anvil1.warp_token') && \ - ANVIL2_WARP=$$(echo $$ADDRESSES | jq -r '.anvil2.warp_token') && \ - SOLVER_ADDR=$$(cast wallet address --private-key $$SOLVER_PRIVATE_KEY) && \ - SOLVER_ADDR_PADDED=$$(printf '0x000000000000000000000000%s' $${SOLVER_ADDR#0x}) && \ - echo " Solver address: $$SOLVER_ADDR" && \ - echo " MockERC20 (anvil1): $$MOCK_USDC" && \ - echo " HypCollateral (anvil1): $$ANVIL1_WARP" && \ - echo " HypSynthetic (anvil2): $$ANVIL2_WARP" && \ - echo " Amount: $(REBALANCE_AMOUNT) (raw)" && \ - echo "" && \ - echo "Step 1: Check initial balances..." && \ - ANVIL1_BAL=$$(cast call $$MOCK_USDC "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL1_RPC 2>/dev/null | cast to-dec 2>/dev/null || echo "0") && \ - ANVIL2_BAL=$$(cast call $$ANVIL2_WARP "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL2_RPC 2>/dev/null | cast to-dec 2>/dev/null || echo "0") && \ - echo " Anvil1 USDC: $$ANVIL1_BAL" && \ - echo " Anvil2 USDC: $$ANVIL2_BAL" && \ - echo "" && \ - echo "Step 2: Derive Celestia forwarding address (dest: anvil1)..." && \ - FORWARD_ADDR=$$(docker exec forwarding-relayer forwarding-relayer derive-address \ - --dest-domain 131337 \ - --dest-recipient $$SOLVER_ADDR_PADDED) && \ - echo " Forwarding address: $$FORWARD_ADDR" && \ - echo "" && \ - echo "Step 3: Register forwarding request with backend..." && \ - REGISTER_RESP=$$(curl -sf -X POST $(FORWARDING_BACKEND)/forwarding-requests \ - -H "Content-Type: application/json" \ - -d "{\"forward_addr\": \"$$FORWARD_ADDR\", \"dest_domain\": 131337, \"dest_recipient\": \"$$SOLVER_ADDR_PADDED\"}") && \ - echo " Response: $$REGISTER_RESP" && \ - echo "" && \ - echo "Step 4: Send USDC to Celestia via HypSynthetic.transferRemote(69420, ...)..." && \ - FORWARD_ADDR_HEX=$$(python3 scripts/bech32_to_bytes32.py "$$FORWARD_ADDR") && \ - echo " Forwarding addr (bytes32): $$FORWARD_ADDR_HEX" && \ - cast send $$ANVIL2_WARP "transferRemote(uint32,bytes32,uint256)" \ - 69420 $$FORWARD_ADDR_HEX $(REBALANCE_AMOUNT) \ - --rpc-url $$ANVIL2_RPC --private-key $$SOLVER_PRIVATE_KEY --value 0 > /dev/null && \ - echo " Sent! anvil2 -> Celestia -> anvil1" && \ - echo "" && \ - echo "Step 5: Waiting for Hyperlane relayer + Celestia forwarding (60s)..." && \ - for i in $$(seq 1 12); do \ - sleep 5; \ - BAL=$$(cast call $$MOCK_USDC "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL1_RPC 2>/dev/null | cast to-dec 2>/dev/null || echo "0"); \ - printf " [%2ds] Anvil1 USDC balance: %s\n" $$((i * 5)) "$$BAL"; \ - if [ "$$BAL" != "$$ANVIL1_BAL" ] && [ "$$BAL" != "0" ]; then \ - echo ""; \ - echo " Tokens arrived!"; \ - break; \ - fi; \ - done && \ - echo "" && \ - echo "Step 6: Final balances..." && \ - ANVIL1_BAL_AFTER=$$(cast call $$MOCK_USDC "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL1_RPC | cast to-dec) && \ - ANVIL2_BAL_AFTER=$$(cast call $$ANVIL2_WARP "balanceOf(address)" $$SOLVER_ADDR --rpc-url $$ANVIL2_RPC | cast to-dec) && \ - echo " Anvil1 USDC: $$ANVIL1_BAL -> $$ANVIL1_BAL_AFTER" && \ - echo " Anvil2 USDC: $$ANVIL2_BAL -> $$ANVIL2_BAL_AFTER" && \ - echo "" && \ - if [ "$$ANVIL1_BAL_AFTER" != "$$ANVIL1_BAL" ]; then \ - echo " Rebalance complete — tokens moved anvil2 -> Celestia -> anvil1"; \ - else \ - echo " Rebalance failed — tokens did not arrive on anvil1"; \ - echo " Check logs: make logs SVC=relayer"; \ - echo " make logs SVC=forwarding-relayer"; \ - fi && \ - echo "" -.PHONY: rebalance-back - # ============================================================================ # Full Setup & Lifecycle # ============================================================================ @@ -503,6 +343,46 @@ mvp: @./mvp.sh .PHONY: mvp +OIF_SERVICES=oif-aggregator oif-solver oif-oracle oif-rebalancer oif-frontend-api oif-frontend + +## service-install: Install all OIF systemd units (requires root). Re-run after repo moves. +service-install: + @REPO=$(shell pwd); \ + for f in scripts/systemd/oif*.service scripts/systemd/oif.target; do \ + dest=/etc/systemd/system/$$(basename $$f); \ + sed "s|/root/solver-cli|$$REPO|g" $$f > $$dest; \ + echo " Installed $$dest"; \ + done + @systemctl daemon-reload + @systemctl enable $(OIF_SERVICES) oif.target + @echo "All units installed. Run: make service-start" +.PHONY: service-install + +## service-start: Start all OIF services +service-start: + @systemctl start oif.target +.PHONY: service-start + +## service-stop: Stop all OIF services +service-stop: + @systemctl stop oif.target +.PHONY: service-stop + +## service-restart: Restart all or one service (SVC=oif-solver to target one) +service-restart: + @systemctl restart $(or $(SVC),oif.target) +.PHONY: service-restart + +## service-status: Show status of all OIF services +service-status: + @systemctl status $(OIF_SERVICES) --no-pager || true +.PHONY: service-status + +## service-logs: Follow logs (SVC=oif-solver for one service, default shows all) +service-logs: + @journalctl -u $(or $(SVC),"oif-*") -f +.PHONY: service-logs + ## clean: Remove generated files and Docker volumes clean: @rm -rf .config diff --git a/scripts/systemd/oif-aggregator.service b/scripts/systemd/oif-aggregator.service new file mode 100644 index 0000000..b43dfbd --- /dev/null +++ b/scripts/systemd/oif-aggregator.service @@ -0,0 +1,18 @@ +[Unit] +Description=OIF Aggregator +After=network.target +PartOf=oif.target + +[Service] +Type=simple +WorkingDirectory=/root/solver-cli/oif/oif-aggregator +EnvironmentFile=/root/solver-cli/.env +Environment=RUST_LOG=info +ExecStart=/root/solver-cli/oif/oif-aggregator/target/release/oif-aggregator +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=oif.target diff --git a/scripts/systemd/oif-frontend-api.service b/scripts/systemd/oif-frontend-api.service new file mode 100644 index 0000000..e06760f --- /dev/null +++ b/scripts/systemd/oif-frontend-api.service @@ -0,0 +1,17 @@ +[Unit] +Description=OIF Frontend API +After=network.target +PartOf=oif.target + +[Service] +Type=simple +WorkingDirectory=/root/solver-cli/frontend +EnvironmentFile=/root/solver-cli/.env +ExecStart=node /root/solver-cli/frontend/server.js +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=oif.target diff --git a/scripts/systemd/oif-frontend.service b/scripts/systemd/oif-frontend.service new file mode 100644 index 0000000..430421c --- /dev/null +++ b/scripts/systemd/oif-frontend.service @@ -0,0 +1,17 @@ +[Unit] +Description=OIF Frontend (Vite) +After=oif-frontend-api.service +PartOf=oif.target + +[Service] +Type=simple +WorkingDirectory=/root/solver-cli/frontend +ExecStartPre=npm install --silent +ExecStart=npx vite --host +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=oif.target diff --git a/scripts/systemd/oif-oracle.service b/scripts/systemd/oif-oracle.service new file mode 100644 index 0000000..45fcd19 --- /dev/null +++ b/scripts/systemd/oif-oracle.service @@ -0,0 +1,19 @@ +[Unit] +Description=OIF Oracle Operator +After=network.target +PartOf=oif.target + +[Service] +Type=simple +WorkingDirectory=/root/solver-cli/oracle-operator +EnvironmentFile=/root/solver-cli/.env +Environment=ORACLE_CONFIG=/root/solver-cli/.config/oracle.toml +Environment=RUST_LOG=info +ExecStart=/root/solver-cli/oracle-operator/target/release/oracle-operator +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=oif.target diff --git a/scripts/systemd/oif-rebalancer.service b/scripts/systemd/oif-rebalancer.service new file mode 100644 index 0000000..cb4ed5e --- /dev/null +++ b/scripts/systemd/oif-rebalancer.service @@ -0,0 +1,17 @@ +[Unit] +Description=OIF Rebalancer +After=network.target +PartOf=oif.target + +[Service] +Type=simple +WorkingDirectory=/root/solver-cli +EnvironmentFile=/root/solver-cli/.env +ExecStart=/root/solver-cli/target/release/solver-cli rebalancer start +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=oif.target diff --git a/scripts/systemd/oif-solver.service b/scripts/systemd/oif-solver.service new file mode 100644 index 0000000..e7ae58e --- /dev/null +++ b/scripts/systemd/oif-solver.service @@ -0,0 +1,17 @@ +[Unit] +Description=OIF Solver +After=network.target oif-aggregator.service +PartOf=oif.target + +[Service] +Type=simple +WorkingDirectory=/root/solver-cli +EnvironmentFile=/root/solver-cli/.env +ExecStart=/root/solver-cli/target/release/solver-cli solver start +Restart=on-failure +RestartSec=5 +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=oif.target diff --git a/scripts/systemd/oif.target b/scripts/systemd/oif.target new file mode 100644 index 0000000..99212c1 --- /dev/null +++ b/scripts/systemd/oif.target @@ -0,0 +1,6 @@ +[Unit] +Description=OIF Services +Wants=oif-aggregator.service oif-solver.service oif-oracle.service oif-rebalancer.service oif-frontend-api.service oif-frontend.service + +[Install] +WantedBy=multi-user.target diff --git a/solver-cli/src/commands/configure.rs b/solver-cli/src/commands/configure.rs index efdd327..00e4452 100644 --- a/solver-cli/src/commands/configure.rs +++ b/solver-cli/src/commands/configure.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Context, Result}; use clap::Args; use std::env; use std::path::PathBuf; @@ -78,6 +78,39 @@ impl ConfigureCommand { print_address("Solver address", &format!("{:?}", solver_address)); + // Derive operator address from env if not already set in state + if state.solver.operator_address.is_none() { + let operator_address = match crate::utils::OracleSignerConfig::from_env()? { + crate::utils::OracleSignerConfig::AwsKms { + key_id, + region, + endpoint, + } => { + use alloy::signers::aws::AwsSigner; + use alloy::signers::Signer; + use aws_sdk_kms::config::Region; + let mut loader = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region(Region::new(region)); + if let Some(ep) = endpoint { + loader = loader.endpoint_url(ep); + } + let sdk_config = loader.load().await; + let client = aws_sdk_kms::Client::new(&sdk_config); + let signer = AwsSigner::new(client, key_id, None) + .await + .map_err(|e| anyhow::anyhow!("Oracle KMS initialization failed: {e}"))?; + format!("{:?}", Signer::address(&signer)) + } + crate::utils::OracleSignerConfig::Env => { + let operator_pk = std::env::var("ORACLE_OPERATOR_PK") + .context("Missing ORACLE_OPERATOR_PK — set it in .env or state")?; + format!("{:?}", ChainClient::address_from_pk(&operator_pk)?) + } + }; + print_address("Operator address (from env)", &operator_address); + state.solver.operator_address = Some(operator_address); + } + // Update solver config in state state.solver.address = Some(format!("{:?}", solver_address)); state.solver.solver_id = Some(self.solver_id.clone()); From db0de70c11b30cf984839d71900241ef7ec05532 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Wed, 11 Mar 2026 15:24:14 +0100 Subject: [PATCH 21/34] build all command --- Makefile | 6 ++++++ scripts/systemd/oif-aggregator.service | 1 + scripts/systemd/oif-frontend-api.service | 1 + scripts/systemd/oif-frontend.service | 1 + scripts/systemd/oif-oracle.service | 1 + 5 files changed, 10 insertions(+) diff --git a/Makefile b/Makefile index d0b7d3b..056342e 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,12 @@ build: @cd solver-cli && cargo build --release --features solver-runtime .PHONY: build +## build-all: Build all service binaries (solver-cli, oracle-operator, oif-aggregator) +build-all: build + @cd oracle-operator && cargo build --release + @cd oif/oif-aggregator && cargo build --release +.PHONY: build-all + ## fmt: Format all Rust workspace crates with rustfmt fmt: @cargo fmt --all diff --git a/scripts/systemd/oif-aggregator.service b/scripts/systemd/oif-aggregator.service index b43dfbd..185bf2b 100644 --- a/scripts/systemd/oif-aggregator.service +++ b/scripts/systemd/oif-aggregator.service @@ -7,6 +7,7 @@ PartOf=oif.target Type=simple WorkingDirectory=/root/solver-cli/oif/oif-aggregator EnvironmentFile=/root/solver-cli/.env +Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Environment=RUST_LOG=info ExecStart=/root/solver-cli/oif/oif-aggregator/target/release/oif-aggregator Restart=on-failure diff --git a/scripts/systemd/oif-frontend-api.service b/scripts/systemd/oif-frontend-api.service index e06760f..fb97ffb 100644 --- a/scripts/systemd/oif-frontend-api.service +++ b/scripts/systemd/oif-frontend-api.service @@ -7,6 +7,7 @@ PartOf=oif.target Type=simple WorkingDirectory=/root/solver-cli/frontend EnvironmentFile=/root/solver-cli/.env +Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ExecStart=node /root/solver-cli/frontend/server.js Restart=on-failure RestartSec=5 diff --git a/scripts/systemd/oif-frontend.service b/scripts/systemd/oif-frontend.service index 430421c..be55411 100644 --- a/scripts/systemd/oif-frontend.service +++ b/scripts/systemd/oif-frontend.service @@ -6,6 +6,7 @@ PartOf=oif.target [Service] Type=simple WorkingDirectory=/root/solver-cli/frontend +Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin ExecStartPre=npm install --silent ExecStart=npx vite --host Restart=on-failure diff --git a/scripts/systemd/oif-oracle.service b/scripts/systemd/oif-oracle.service index 45fcd19..dc65203 100644 --- a/scripts/systemd/oif-oracle.service +++ b/scripts/systemd/oif-oracle.service @@ -7,6 +7,7 @@ PartOf=oif.target Type=simple WorkingDirectory=/root/solver-cli/oracle-operator EnvironmentFile=/root/solver-cli/.env +Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Environment=ORACLE_CONFIG=/root/solver-cli/.config/oracle.toml Environment=RUST_LOG=info ExecStart=/root/solver-cli/oracle-operator/target/release/oracle-operator From 991327473d885bd0096597d7ee860be7197f2a84 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Wed, 11 Mar 2026 16:42:45 +0100 Subject: [PATCH 22/34] fix nonce issue --- frontend/server.js | 3 ++- frontend/src/App.tsx | 2 +- rebalancer/src/client.rs | 43 ++++++++++++++++++++++++++++++ rebalancer/src/service.rs | 22 +++++++++++---- scripts/systemd/oif-oracle.service | 2 +- 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/frontend/server.js b/frontend/server.js index 24110db..1530bac 100644 --- a/frontend/server.js +++ b/frontend/server.js @@ -132,7 +132,7 @@ function parseWarpRouteYaml(yamlContent) { function getWarpRouteConfig(token) { const state = readState(); const hypAddresses = readHyperlaneAddresses(); - if (!hypAddresses) throw new Error('Hyperlane not deployed — hyperlane-addresses.json not found'); + if (!hypAddresses) return null; const celestiaDomainId = hypAddresses.celestiadev?.domain_id || 69420; @@ -449,6 +449,7 @@ app.post('/api/bridge/prepare', async (req, res) => { if (!address) return res.status(400).json({ error: '"address" (user wallet) is required' }); try { const warp = getWarpRouteConfig(token); + if (!warp) return res.status(503).json({ error: 'Slow route unavailable: Hyperlane not deployed on this network' }); const src = warp.chains[from]; const dst = warp.chains[to]; if (!src) throw new Error(`Chain "${from}" not found in ${token} warp route`); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3a8ee78..862feea 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -184,7 +184,7 @@ export default function App() { await writeContractAsync({ address: token.address as `0x${string}`, abi: parseAbi(['function approve(address, uint256) returns (bool)']), - functionName: 'approve', args: [spender, 100000000n], chainId: fromId, + functionName: 'approve', args: [spender, BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')], chainId: fromId, }) } const types = { ...payload.types } diff --git a/rebalancer/src/client.rs b/rebalancer/src/client.rs index 8cfabda..bd58331 100644 --- a/rebalancer/src/client.rs +++ b/rebalancer/src/client.rs @@ -41,6 +41,7 @@ sol! { #[sol(rpc)] interface IERC20 { function balanceOf(address account) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); } @@ -187,6 +188,48 @@ impl ChainClient { Ok(*pending.tx_hash()) } + /// Approve spender for MaxUint256 only if current allowance is below `required`. + /// Returns Some(tx_hash) if an approval was submitted, None if already sufficient. + pub async fn ensure_allowance( + &self, + token: Address, + spender: Address, + required: U256, + ) -> Result> { + let call = IERC20::allowanceCall { + owner: self.account, + spender, + }; + let tx = TransactionRequest::default() + .to(token) + .input(Bytes::from(call.abi_encode()).into()); + let raw = self + .provider + .call(tx) + .await + .context("Failed to call ERC20 allowance")?; + let current = U256::from_be_slice(&raw); + + if current >= required { + return Ok(None); + } + + let max = U256::MAX; + let tx_hash = self.approve_erc20(token, spender, max).await?; + Ok(Some(tx_hash)) + } + + pub async fn wait_for_tx(&self, tx_hash: TxHash) -> Result<()> { + use alloy::providers::PendingTransactionConfig; + self.provider + .watch_pending_transaction(PendingTransactionConfig::new(tx_hash)) + .await + .context("Failed to watch pending transaction")? + .await + .with_context(|| format!("Transaction {} was not mined", tx_hash))?; + Ok(()) + } + pub async fn quote_transfer_remote( &self, source_router: Address, diff --git a/rebalancer/src/service.rs b/rebalancer/src/service.rs index b158bfd..4b3aeba 100644 --- a/rebalancer/src/service.rs +++ b/rebalancer/src/service.rs @@ -502,21 +502,33 @@ impl RebalancerService { if source_token_config.asset_type == AssetType::Erc20 { if let Some(erc20_address) = source_token_config.address { match source_client - .approve_erc20(erc20_address, source_collateral_token, transfer_amount) + .ensure_allowance(erc20_address, source_collateral_token, transfer_amount) .await { - Ok(tx_hash) => { + Ok(Some(tx_hash)) => { info!( - "Asset {} ERC20 approve submitted: route {} -> {} token={} spender={} amount={} {} tx_hash={}", + "Asset {} ERC20 approve submitted: route {} -> {} token={} spender={} tx_hash={}", asset.symbol, source_chain.name, destination_chain.name, erc20_address, source_collateral_token, - format_raw_u128(transfer.amount_raw, asset.decimals), - asset.symbol, tx_hash, ); + // Wait for approval to be mined before submitting transferRemote + if let Err(err) = source_client.wait_for_tx(tx_hash).await { + warn!( + "Asset {} route {} -> {} ERC20 approve tx not confirmed; skipping transfer:\n{:#}", + asset.symbol, source_chain.name, destination_chain.name, err + ); + continue; + } + } + Ok(None) => { + debug!( + "Asset {} route {} -> {}: allowance already sufficient, skipping approve", + asset.symbol, source_chain.name, destination_chain.name, + ); } Err(err) => { warn!( diff --git a/scripts/systemd/oif-oracle.service b/scripts/systemd/oif-oracle.service index dc65203..8582fdc 100644 --- a/scripts/systemd/oif-oracle.service +++ b/scripts/systemd/oif-oracle.service @@ -10,7 +10,7 @@ EnvironmentFile=/root/solver-cli/.env Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin Environment=ORACLE_CONFIG=/root/solver-cli/.config/oracle.toml Environment=RUST_LOG=info -ExecStart=/root/solver-cli/oracle-operator/target/release/oracle-operator +ExecStart=/root/solver-cli/target/release/oracle-operator Restart=on-failure RestartSec=5 StandardOutput=journal From 4f385e1e75837dd083c587617388b272c512cd51 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 11:59:56 +0100 Subject: [PATCH 23/34] fixes --- Cargo.lock | 104 +++++++++++++-------------- frontend/src/App.tsx | 77 +++++++++++++++----- hyperlane/configs/warp-config.yaml-e | 2 +- solver-cli/Cargo.toml | 22 +++--- solver-settlement/Cargo.toml | 6 +- 5 files changed, 125 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aea0d58..ca6dad0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39" +checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757" dependencies = [ "alloy-primitives", "num_enum", @@ -828,13 +828,12 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7fd448ab0a017de542de1dcca7a58e7019fe0e7a34ed3f9543ebddf6aceffa" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" dependencies = [ "alloy-primitives", "alloy-rlp", - "arrayvec", "derive_more", "nybbles", "serde", @@ -1123,15 +1122,12 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -dependencies = [ - "serde", -] [[package]] name = "assert_cmd" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" +checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" dependencies = [ "anstyle", "bstr", @@ -1894,9 +1890,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.6" +version = "2.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0f582957c24870b7bfd12bf562c40b4734b533cafbaf8ded31d6d85f462c01" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" dependencies = [ "blst", "cc", @@ -3115,7 +3111,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -3493,9 +3489,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libm" @@ -3773,9 +3769,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.75" +version = "0.10.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" dependencies = [ "bitflags", "cfg-if", @@ -3805,9 +3801,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.111" +version = "0.9.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" dependencies = [ "cc", "libc", @@ -4239,7 +4235,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.37", - "socket2 0.6.2", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -4248,9 +4244,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "bytes", "getrandom 0.3.4", @@ -4276,7 +4272,7 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.6.2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] @@ -4432,7 +4428,7 @@ dependencies = [ "pin-project-lite", "ryu", "sha1_smol", - "socket2 0.6.2", + "socket2 0.6.3", "tokio", "tokio-util", "url", @@ -4819,9 +4815,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] @@ -5253,18 +5249,18 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "solver-account" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-consensus", "alloy-network", @@ -5338,7 +5334,7 @@ dependencies = [ [[package]] name = "solver-config" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "dotenvy", "regex", @@ -5353,7 +5349,7 @@ dependencies = [ [[package]] name = "solver-core" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-primitives", "chrono", @@ -5368,7 +5364,7 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f)", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f)", "solver-storage", "solver-types", "thiserror 2.0.18", @@ -5380,7 +5376,7 @@ dependencies = [ [[package]] name = "solver-delivery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-consensus", "alloy-network", @@ -5409,7 +5405,7 @@ dependencies = [ [[package]] name = "solver-discovery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5439,7 +5435,7 @@ dependencies = [ [[package]] name = "solver-order" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5459,7 +5455,7 @@ dependencies = [ [[package]] name = "solver-pricing" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-primitives", "async-trait", @@ -5477,7 +5473,7 @@ dependencies = [ [[package]] name = "solver-service" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5507,7 +5503,7 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f)", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f)", "solver-storage", "solver-types", "subtle", @@ -5532,7 +5528,7 @@ dependencies = [ "async-trait", "serde_json", "sha3", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f)", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f)", "solver-storage", "solver-types", "tokio", @@ -5543,7 +5539,7 @@ dependencies = [ [[package]] name = "solver-settlement" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-primitives", "alloy-provider", @@ -5568,7 +5564,7 @@ dependencies = [ [[package]] name = "solver-storage" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "async-trait", "chrono", @@ -5588,7 +5584,7 @@ dependencies = [ [[package]] name = "solver-types" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" dependencies = [ "alloy-consensus", "alloy-contract", @@ -5751,9 +5747,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.26.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", @@ -5894,7 +5890,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] @@ -6361,9 +6357,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -6913,9 +6909,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.14" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] @@ -7073,18 +7069,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 862feea..889579e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -35,6 +35,43 @@ function normalizeStatus(status: unknown): string { // ── Primitives ──────────────────────────────────────────────────────────────── +function fallbackCopy(text: string) { + const el = document.createElement('textarea') + el.value = text + el.style.position = 'fixed' + el.style.opacity = '0' + document.body.appendChild(el) + el.select() + document.execCommand('copy') + document.body.removeChild(el) +} + +function CopyableAddress({ address, className }: { address: string; className?: string }) { + const [copied, setCopied] = useState(false) + const handleCopy = (e: React.MouseEvent) => { + e.stopPropagation() + const done = () => { setCopied(true); setTimeout(() => setCopied(false), 1500) } + try { + if (navigator?.clipboard?.writeText) { + navigator.clipboard.writeText(address).then(done).catch(() => { fallbackCopy(address); done() }) + } else { + fallbackCopy(address); done() + } + } catch { fallbackCopy(address); done() } + } + return ( + + {truncAddr(address)} + + + ) +} + function Spinner({ size = 16 }: { size?: number }) { return ( @@ -208,12 +245,13 @@ export default function App() { try { const s = await api.orderStatus(id) setOrderStatus(s) + loadBalances() const n = normalizeStatus(s.status) - if (n === 'finalized' || n === 'failed') { - clearInterval(pollRef.current); setStep('done'); loadBalances() + if (n === 'finalized' || n === 'failed' || s.settlement?.fillTransaction) { + clearInterval(pollRef.current); setStep('done') } } catch {} - }, 3000) + }, 1000) } useEffect(() => () => { if (pollRef.current) clearInterval(pollRef.current) }, []) @@ -346,7 +384,7 @@ export default function App() {
- {truncAddr(connectedAddress!)} +
@@ -819,18 +853,27 @@ export default function App() {
System
+ {config?.userAddress && ( +
+ User + +
+ )} + {config?.solverAddress && ( +
+ Solver + +
+ )} {[ - { label: 'User', value: config ? truncAddr(config.userAddress) : '—', mono: true }, - { label: 'Solver', value: config?.solverAddress ? truncAddr(config.solverAddress) : '—', mono: true }, - { label: 'Chains', value: String(chainEntries.length), mono: false }, + { label: 'Chains', value: String(chainEntries.length), status: undefined }, { label: 'Backend', value: health.backend, status: health.backend }, { label: 'Aggregator', value: health.aggregator, status: health.aggregator }, - ].map(({ label, value, mono, status }) => ( + ].map(({ label, value, status }) => (
{label} {value} diff --git a/hyperlane/configs/warp-config.yaml-e b/hyperlane/configs/warp-config.yaml-e index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml-e +++ b/hyperlane/configs/warp-config.yaml-e @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index 749e0be..ddf7b03 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -92,23 +92,23 @@ futures = { version = "0.3", optional = true } nix = { version = "0.28", features = ["signal", "process"] } rebalancer = { path = "../rebalancer" } sha3 = { version = "0.10", optional = true } -solver-account = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } -solver-config = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } -solver-core = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } -solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } -solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } -solver-order = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } -solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } -solver-service = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } +solver-account = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-config = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-core = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-order = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-service = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } solver-settlement = { path = "../solver-settlement", optional = true } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", optional = true } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", features = ["oif-interfaces"], optional = true } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", features = ["oif-interfaces"], optional = true } [dev-dependencies] assert_cmd = "2.0" mockall = "0.13" predicates = "3.0" solver-settlement = { path = "../solver-settlement", features = ["testing"] } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", features = ["testing"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", features = ["testing"] } tempfile = "3.8" diff --git a/solver-settlement/Cargo.toml b/solver-settlement/Cargo.toml index 57d1337..2d58af9 100644 --- a/solver-settlement/Cargo.toml +++ b/solver-settlement/Cargo.toml @@ -14,9 +14,9 @@ alloy-sol-types = { version = "1.0.37" } async-trait = "0.1" serde_json = "1.0" sha3 = "0.10" -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f", features = ["oif-interfaces"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f" } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", features = ["oif-interfaces"] } tokio = { version = "1", features = ["rt"] } toml = "0.9" tracing = "0.1" -upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", rev = "2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" } +upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f" } From 560ada69533006ea44e332cbfe7231301ecb2962 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 12:09:57 +0100 Subject: [PATCH 24/34] wip: fix frontend --- frontend/src/App.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 889579e..c8e8382 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -241,15 +241,15 @@ export default function App() { const startPolling = (id: string) => { if (pollRef.current) clearInterval(pollRef.current) + const deadline = Date.now() + 60_000 pollRef.current = setInterval(async () => { try { const s = await api.orderStatus(id) setOrderStatus(s) loadBalances() const n = normalizeStatus(s.status) - if (n === 'finalized' || n === 'failed' || s.settlement?.fillTransaction) { - clearInterval(pollRef.current); setStep('done') - } + const isDone = n === 'finalized' || n === 'claimed' || n === 'failed' || s.settlement?.fillTransaction || Date.now() > deadline + if (isDone) { clearInterval(pollRef.current); setStep('done') } } catch {} }, 1000) } @@ -603,7 +603,7 @@ export default function App() { {/* Order status (fast route only) */} {routeType === 'fast' && (step === 'polling' || step === 'done') && orderStatus && (() => { const status = normalizeStatus(orderStatus.status) - const ok = status === 'finalized' + const ok = status === 'finalized' || status === 'claimed' const fail = status === 'failed' return (
Date: Thu, 12 Mar 2026 12:17:06 +0100 Subject: [PATCH 25/34] fix address truncation --- Cargo.lock | 28 ++++++++++++++-------------- solver-cli/Cargo.toml | 22 +++++++++++----------- solver-settlement/Cargo.toml | 6 +++--- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca6dad0..bca0fb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5260,7 +5260,7 @@ dependencies = [ [[package]] name = "solver-account" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-consensus", "alloy-network", @@ -5334,7 +5334,7 @@ dependencies = [ [[package]] name = "solver-config" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "dotenvy", "regex", @@ -5349,7 +5349,7 @@ dependencies = [ [[package]] name = "solver-core" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-primitives", "chrono", @@ -5364,7 +5364,7 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f)", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", "solver-storage", "solver-types", "thiserror 2.0.18", @@ -5376,7 +5376,7 @@ dependencies = [ [[package]] name = "solver-delivery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-consensus", "alloy-network", @@ -5405,7 +5405,7 @@ dependencies = [ [[package]] name = "solver-discovery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5435,7 +5435,7 @@ dependencies = [ [[package]] name = "solver-order" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5455,7 +5455,7 @@ dependencies = [ [[package]] name = "solver-pricing" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-primitives", "async-trait", @@ -5473,7 +5473,7 @@ dependencies = [ [[package]] name = "solver-service" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5503,7 +5503,7 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f)", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", "solver-storage", "solver-types", "subtle", @@ -5528,7 +5528,7 @@ dependencies = [ "async-trait", "serde_json", "sha3", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f)", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", "solver-storage", "solver-types", "tokio", @@ -5539,7 +5539,7 @@ dependencies = [ [[package]] name = "solver-settlement" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-primitives", "alloy-provider", @@ -5564,7 +5564,7 @@ dependencies = [ [[package]] name = "solver-storage" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "async-trait", "chrono", @@ -5584,7 +5584,7 @@ dependencies = [ [[package]] name = "solver-types" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2dd99c92b68caa1ae9d6f0ed07704507350de04f#2dd99c92b68caa1ae9d6f0ed07704507350de04f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-consensus", "alloy-contract", diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index ddf7b03..316a77b 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -92,23 +92,23 @@ futures = { version = "0.3", optional = true } nix = { version = "0.28", features = ["signal", "process"] } rebalancer = { path = "../rebalancer" } sha3 = { version = "0.10", optional = true } -solver-account = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } -solver-config = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } -solver-core = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } -solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } -solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } -solver-order = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } -solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } -solver-service = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } +solver-account = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } +solver-config = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } +solver-core = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } +solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } +solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } +solver-order = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } +solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } +solver-service = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } solver-settlement = { path = "../solver-settlement", optional = true } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", optional = true } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", features = ["oif-interfaces"], optional = true } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", features = ["oif-interfaces"], optional = true } [dev-dependencies] assert_cmd = "2.0" mockall = "0.13" predicates = "3.0" solver-settlement = { path = "../solver-settlement", features = ["testing"] } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", features = ["testing"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", features = ["testing"] } tempfile = "3.8" diff --git a/solver-settlement/Cargo.toml b/solver-settlement/Cargo.toml index 2d58af9..3931b4c 100644 --- a/solver-settlement/Cargo.toml +++ b/solver-settlement/Cargo.toml @@ -14,9 +14,9 @@ alloy-sol-types = { version = "1.0.37" } async-trait = "0.1" serde_json = "1.0" sha3 = "0.10" -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f" } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f", features = ["oif-interfaces"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc" } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", features = ["oif-interfaces"] } tokio = { version = "1", features = ["rt"] } toml = "0.9" tracing = "0.1" -upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", rev = "2dd99c92b68caa1ae9d6f0ed07704507350de04f" } +upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc" } From b45bd9b9109e35be157821c7a39b142077b2c8fe Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 14:25:27 +0100 Subject: [PATCH 26/34] fixes --- Makefile | 2 +- frontend/index.html | 2 +- frontend/server.js | 48 ++++++- frontend/src/App.tsx | 133 +++++++++++++----- frontend/src/api.ts | 16 ++- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/configs/warp-config.yaml-e | 2 +- hyperlane/hyperlane-addresses.json | 29 ++++ hyperlane/hyperlane-cosmosnative.json | 8 ++ .../registry/chains/anvil1/addresses.yaml | 14 ++ .../registry/chains/anvil2/addresses.yaml | 14 ++ .../warp_routes/USDC/warp-config-config.yaml | 19 +++ .../warp_routes/USDC/warp-config-deploy.yaml | 15 ++ solver-cli/src/commands/configure.rs | 14 +- solver-cli/src/solver/config_gen.rs | 20 +++ 15 files changed, 282 insertions(+), 56 deletions(-) create mode 100644 hyperlane/hyperlane-addresses.json create mode 100644 hyperlane/hyperlane-cosmosnative.json create mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml create mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/Makefile b/Makefile index 056342e..657e411 100644 --- a/Makefile +++ b/Makefile @@ -213,7 +213,7 @@ fund-user: cast send --rpc-url $$ANVIL1_RPC --private-key $$ANVIL1_PK --value 10ether $$USER_ADDR 2>/dev/null && \ if [ ! -z "$$ANVIL2_RPC" ]; then \ echo " Funding on Anvil2 (10 ETH)..." && \ - cast send --rpc-url $$ANVIL2_RPC --private-key $$ANVIL2_PK --value 10ether $$USER_ADDR 2>/dev/null; \ + cast send --rpc-url $$ANVIL2_RPC --private-key $$ANVIL2_PK --value 10ether $$USER_ADDR; \ fi && \ echo "User funded" .PHONY: fund-user diff --git a/frontend/index.html b/frontend/index.html index 3722fe3..f2a9ab5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + OIF Solver diff --git a/frontend/server.js b/frontend/server.js index 1530bac..a64b8e1 100644 --- a/frontend/server.js +++ b/frontend/server.js @@ -230,11 +230,11 @@ function isOriginChain(chainName, hypAddresses) { function mapAggregatorQuoteError(msg) { if (typeof msg !== 'string') return JSON.stringify(msg); const lower = msg.toLowerCase(); - if (lower.includes('all solvers failed')) { - return 'SOLVER_REJECTED: No solver could fill this transfer — the amount is likely too small to cover gas and bridging fees. Try a larger amount.'; + if (lower.includes('all solvers failed') || lower.includes('no quotes')) { + return `SOLVER_REJECTED: ${msg}`; } - if (lower.includes('no solvers available')) { - return 'SOLVER_OFFLINE: No solvers are available for this route. Make sure the solver is running (make solver).'; + if (lower.includes('no solvers available') || lower.includes('solver offline')) { + return `SOLVER_OFFLINE: ${msg}`; } return msg; } @@ -265,12 +265,28 @@ app.get('/api/config', (_req, res) => { const user = getUserAccount(); const chains = {}; + // Build warp route fallback tokens (same logic as /api/balances) + const warpTokenByChainName = {}; + try { + for (const symbol of ['USDC']) { + const warp = getWarpRouteConfig(symbol); + if (!warp) continue; + for (const [chainName, info] of Object.entries(warp.chains)) { + if (!warpTokenByChainName[chainName]) warpTokenByChainName[chainName] = {}; + const queryAddr = info.warpType === 'collateral' ? info.underlying : info.warpToken; + if (queryAddr) warpTokenByChainName[chainName][symbol] = { address: queryAddr, symbol, decimals: 6, token_type: 'erc20' }; + } + } + } catch {} + for (const [chainId, chain] of Object.entries(state.chains)) { + const warpFallback = warpTokenByChainName[chain.name] ?? {}; + const tokens = Object.keys(chain.tokens).length > 0 ? chain.tokens : warpFallback; chains[chainId] = { name: chain.name, chainId: chain.chain_id, rpc: chain.rpc, - tokens: chain.tokens, + tokens, contracts: chain.contracts || {}, }; } @@ -308,12 +324,32 @@ app.get('/api/balances', async (req, res) => { console.log(`[balances] user=${userAddress} solver=${state.solver?.address}`); const result = {}; + // Build warp route fallback: chainName -> symbol -> { address, decimals } + // Used when state.json tokens are missing (e.g. race condition or testnet chains) + const warpTokenByChainName = {}; + try { + for (const symbol of ['USDC']) { + const warp = getWarpRouteConfig(symbol); + if (!warp) continue; + for (const [chainName, info] of Object.entries(warp.chains)) { + if (!warpTokenByChainName[chainName]) warpTokenByChainName[chainName] = {}; + // For balance queries: collateral chain → underlying ERC20; synthetic → warpToken + const queryAddr = info.warpType === 'collateral' ? info.underlying : info.warpToken; + if (queryAddr) warpTokenByChainName[chainName][symbol] = { address: queryAddr, decimals: 6 }; + } + } + } catch {} + const promises = Object.entries(state.chains).map(async ([chainId, chain]) => { const client = createPublicClient({ transport: http(chain.rpc) }); const chainBal = { user: {}, solver: {} }; + // Merge state tokens with warp route fallback (state takes precedence) + const warpFallback = warpTokenByChainName[chain.name] ?? {}; + const tokensToQuery = { ...warpFallback, ...chain.tokens }; + // Token balances - for (const [symbol, token] of Object.entries(chain.tokens)) { + for (const [symbol, token] of Object.entries(tokensToQuery)) { try { const userBal = await client.readContract({ address: token.address, abi: erc20Abi, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c8e8382..c8fc9fd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -60,14 +60,17 @@ function CopyableAddress({ address, className }: { address: string; className?: } catch { fallbackCopy(address); done() } } return ( - + {truncAddr(address)} - + + {address} + ) } @@ -86,6 +89,7 @@ const CHAIN_GRADIENTS: Record = { anvil2: 'from-cyan-400 to-blue-500', sepolia: 'from-amber-400 to-orange-500', arbitrum: 'from-sky-400 to-blue-500', + eden: 'from-purple-500 to-violet-700', } function ChainBadge({ name }: { name: string }) { @@ -127,6 +131,7 @@ export default function App() { const [slowLoading, setSlowLoading] = useState(false) const [slowMsg, setSlowMsg] = useState('') + const [timedOut, setTimedOut] = useState(false) const [rightTab, setRightTab] = useState<'balances' | 'tools'>('balances') @@ -190,14 +195,26 @@ export default function App() { const fromId = config!.chains[fromChain].chainId const toId = config!.chains[toChain].chainId const raw = Math.round(parseFloat(amount) * 10 ** selectedTokenDecimals).toString() - const resp = await api.quote(fromId, toId, raw, asset, - isConnected && connectedAddress ? connectedAddress : undefined) + let resp: any + try { + resp = await api.quote(fromId, toId, raw, asset, + isConnected && connectedAddress ? connectedAddress : undefined) + } catch (fetchErr: any) { + const msg: string = fetchErr?.message ?? '' + // Only treat as network error if msg hasn't already been categorized by server.js + const alreadyCategorized = msg.startsWith('SOLVER_REJECTED:') || msg.startsWith('SOLVER_OFFLINE:') + const isNetworkErr = !alreadyCategorized && (fetchErr?.name === 'TypeError' || msg.toLowerCase().includes('econnrefused') || msg.toLowerCase().includes('fetch failed')) + throw new Error(isNetworkErr + ? 'SOLVER_OFFLINE: Cannot reach the aggregator. Make sure the solver and aggregator are running (make solver && make aggregator).' + : fetchErr.message) + } if (!resp.quotes?.length) { const meta = (resp as any).metadata + const detail: string = (resp as any).error || (resp as any).message || '' const allFailed = meta && meta.solvers_queried > 0 && meta.solvers_responded_success === 0 throw new Error(allFailed - ? 'SOLVER_REJECTED: No solver could fill this transfer — the amount is likely too small to cover gas and bridging fees. Try a larger amount.' - : 'No quotes returned. Is the solver running? (make solver)') + ? `SOLVER_REJECTED: ${detail || 'No solver could fill this transfer.'}` + : 'SOLVER_OFFLINE: No quotes returned. Is the solver running? (make solver)') } setQuote(resp.quotes[0]); setStep('quoted') } catch (err: any) { setError(err.message); setStep('error') } @@ -243,12 +260,19 @@ export default function App() { if (pollRef.current) clearInterval(pollRef.current) const deadline = Date.now() + 60_000 pollRef.current = setInterval(async () => { + const expired = Date.now() > deadline + if (expired) { + setTimedOut(true) + clearInterval(pollRef.current) + setStep('done') + return + } try { const s = await api.orderStatus(id) setOrderStatus(s) loadBalances() const n = normalizeStatus(s.status) - const isDone = n === 'finalized' || n === 'claimed' || n === 'failed' || s.settlement?.fillTransaction || Date.now() > deadline + const isDone = n === 'finalized' || n === 'executed' || n === 'settling' || n === 'settled' || n === 'failed' || n === 'refunded' || !!s.fillTransaction if (isDone) { clearInterval(pollRef.current); setStep('done') } } catch {} }, 1000) @@ -259,7 +283,7 @@ export default function App() { const resetFlowState = () => { - setStep('idle'); setQuote(null); setOrderId(''); setOrderStatus(null); setError(''); setSlowMsg('') + setStep('idle'); setQuote(null); setOrderId(''); setOrderStatus(null); setError(''); setSlowMsg(''); setTimedOut(false) } // ── Slow route (Celestia bridge, user → user) ───────────────────────────── @@ -603,8 +627,8 @@ export default function App() { {/* Order status (fast route only) */} {routeType === 'fast' && (step === 'polling' || step === 'done') && orderStatus && (() => { const status = normalizeStatus(orderStatus.status) - const ok = status === 'finalized' || status === 'claimed' - const fail = status === 'failed' + const ok = status === 'finalized' || status === 'executed' || status === 'settling' || status === 'settled' + const fail = status === 'failed' || status === 'refunded' return (
)} - {orderStatus.settlement?.fillTransaction && ( + {orderStatus.fillTransaction && (
Fill tx - -
- )} - {orderStatus.settlement?.claimTransaction && ( -
- Claim tx - +
)}
@@ -715,25 +733,71 @@ export default function App() { Awaiting settlement… ) : step === 'done' ? ( - + <> + {timedOut && ( +
+ + + + +

+ Settlement is taking longer than expected — check your balances to see if the transfer completed. +

+
+ )} + + ) : null ) : ( // ── Slow route: direct Celestia bridge ───────────────────── <> - {slowMsg && ( + {/* Step progress (while loading) */} + {slowLoading && (() => { + const steps = [ + { label: 'Preparing route', match: 'Preparing' }, + { label: 'Approving token', match: 'Approving' }, + { label: 'Submitting transaction', match: 'Submitting' }, + ] + const activeIdx = steps.findIndex(s => slowMsg.includes(s.match)) + return ( +
+ {steps.map((s, i) => { + const isDone = activeIdx > i + const isCurrent = activeIdx === i + return ( +
+ {isCurrent + ? + : isDone + ? + : + } + {s.label} +
+ ) + })} +
+ ) + })()} + + {/* Result message (after loading) */} + {slowMsg && !slowLoading && (
{slowMsg}
)} - {slowMsg && !slowMsg.startsWith('Error') ? ( + + {slowMsg && !slowLoading && !slowMsg.startsWith('Error') ? ( )} @@ -800,7 +861,10 @@ export default function App() {
- You +
+ You + {(connectedAddress || config?.userAddress) && } +
{asset && cb.balances.user[asset] && ( @@ -817,7 +881,10 @@ export default function App() {
- Solver +
+ Solver + {config?.solverAddress && } +
{asset && cb.balances.solver[asset] && ( diff --git a/frontend/src/api.ts b/frontend/src/api.ts index c7d1035..86a2958 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -70,14 +70,16 @@ export interface OrderResponse { } export interface OrderStatus { - id: string - status: 'pending' | 'accepted' | 'finalized' | 'failed' - createdAt: number - updatedAt: number + orderId: string + status: 'created' | 'pending' | 'executing' | 'executed' | 'settling' | 'settled' | 'finalized' | 'failed' | 'refunded' | string + createdAt: string + updatedAt: string + fillTransaction?: { hash: string; chainId: number } | Record settlement?: { - status: string - fillTransaction?: { hash: string; chainId: number } - claimTransaction?: { hash: string; chainId: number } + settlementType?: string + sourceChainId?: string + destinationChainId?: string + recipient?: string } } diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/configs/warp-config.yaml-e b/hyperlane/configs/warp-config.yaml-e index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml-e +++ b/hyperlane/configs/warp-config.yaml-e @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json new file mode 100644 index 0000000..64f24e3 --- /dev/null +++ b/hyperlane/hyperlane-addresses.json @@ -0,0 +1,29 @@ +{ + "anvil1": { + "chain_id": 31337, + "domain_id": 131337, + "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "warp_token_type": "collateral", + "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "anvil2": { + "chain_id": 31338, + "domain_id": 31338, + "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "warp_token_type": "synthetic", + "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + }, + "celestiadev": { + "domain_id": 69420, + "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" + } +} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json new file mode 100644 index 0000000..ae2a37b --- /dev/null +++ b/hyperlane/hyperlane-cosmosnative.json @@ -0,0 +1,8 @@ +{ + "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", + "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", + "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" +} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml new file mode 100644 index 0000000..86b298a --- /dev/null +++ b/hyperlane/registry/chains/anvil1/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" +merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" +validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml new file mode 100644 index 0000000..ad2a018 --- /dev/null +++ b/hyperlane/registry/chains/anvil2/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" +proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml new file mode 100644 index 0000000..e274db1 --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../schema.json +tokens: + - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + chainName: anvil2 + connections: + - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 + decimals: 6 + name: USDC + standard: EvmHypSynthetic + symbol: USDC + - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + chainName: anvil1 + collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + connections: + - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 + decimals: 6 + name: USDC + standard: EvmHypCollateral + symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml new file mode 100644 index 0000000..dec3a6a --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml @@ -0,0 +1,15 @@ +anvil1: + decimals: 6 + mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + type: collateral +anvil2: + decimals: 6 + mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + type: synthetic diff --git a/solver-cli/src/commands/configure.rs b/solver-cli/src/commands/configure.rs index 00e4452..007d4a2 100644 --- a/solver-cli/src/commands/configure.rs +++ b/solver-cli/src/commands/configure.rs @@ -145,13 +145,15 @@ impl ConfigureCommand { aggregator_config_path )); - // Generate rebalancer config + // Generate rebalancer config (optional — skipped if token coverage is insufficient) let rebalancer_config_path = project_dir.join(".config/rebalancer.toml"); - RebalancerConfigGenerator::write_config(&state, &rebalancer_config_path).await?; - print_success(&format!( - "Rebalancer config written to {:?}", - rebalancer_config_path - )); + match RebalancerConfigGenerator::write_config(&state, &rebalancer_config_path).await { + Ok(()) => print_success(&format!( + "Rebalancer config written to {:?}", + rebalancer_config_path + )), + Err(e) => print_info(&format!("Skipping rebalancer config: {e}")), + } // Generate Hyperlane relayer config let hyperlane_config_path = project_dir.join(".config/hyperlane-relayer.json"); diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index c6c47f4..69f41ee 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -181,6 +181,26 @@ api_host = "127.0.0.1" api_port = 5002 network_ids = [{chain_ids_str}] +# ============================================================================ +# GAS ESTIMATES (per flow, in gas units) +# ============================================================================ +[gas] + +[gas.flows.resource_lock] +open = 0 +fill = 77298 +claim = 122793 + +[gas.flows.permit2_escrow] +open = 146306 +fill = 77298 +claim = 60084 + +[gas.flows.eip3009_escrow] +open = 130254 +fill = 77298 +claim = 60084 + # ============================================================================ # ORDER # ============================================================================ From cfe38a91292817fc6de34db188f414e08343421d Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 14:26:10 +0100 Subject: [PATCH 27/34] cleanup --- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/configs/warp-config.yaml-e | 2 +- hyperlane/hyperlane-addresses.json | 29 ------------------- hyperlane/hyperlane-cosmosnative.json | 8 ----- .../registry/chains/anvil1/addresses.yaml | 14 --------- .../registry/chains/anvil2/addresses.yaml | 14 --------- .../warp_routes/USDC/warp-config-config.yaml | 19 ------------ .../warp_routes/USDC/warp-config-deploy.yaml | 15 ---------- 8 files changed, 2 insertions(+), 101 deletions(-) delete mode 100644 hyperlane/hyperlane-addresses.json delete mode 100644 hyperlane/hyperlane-cosmosnative.json delete mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml delete mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/configs/warp-config.yaml-e b/hyperlane/configs/warp-config.yaml-e index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml-e +++ b/hyperlane/configs/warp-config.yaml-e @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json deleted file mode 100644 index 64f24e3..0000000 --- a/hyperlane/hyperlane-addresses.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "anvil1": { - "chain_id": 31337, - "domain_id": 131337, - "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", - "warp_token_type": "collateral", - "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" - }, - "anvil2": { - "chain_id": 31338, - "domain_id": 31338, - "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "warp_token_type": "synthetic", - "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" - }, - "celestiadev": { - "domain_id": 69420, - "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", - "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" - } -} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json deleted file mode 100644 index ae2a37b..0000000 --- a/hyperlane/hyperlane-cosmosnative.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", - "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", - "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" -} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml deleted file mode 100644 index 86b298a..0000000 --- a/hyperlane/registry/chains/anvil1/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" -merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" -testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" -validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml deleted file mode 100644 index ad2a018..0000000 --- a/hyperlane/registry/chains/anvil2/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" -proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" -staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml deleted file mode 100644 index e274db1..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# yaml-language-server: $schema=../schema.json -tokens: - - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" - chainName: anvil2 - connections: - - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 - decimals: 6 - name: USDC - standard: EvmHypSynthetic - symbol: USDC - - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" - chainName: anvil1 - collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - connections: - - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 - decimals: 6 - name: USDC - standard: EvmHypCollateral - symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml deleted file mode 100644 index dec3a6a..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml +++ /dev/null @@ -1,15 +0,0 @@ -anvil1: - decimals: 6 - mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - type: collateral -anvil2: - decimals: 6 - mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - type: synthetic From c6fb00c97ebfa28c9a255b0e55219648f1dd545c Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 14:33:47 +0100 Subject: [PATCH 28/34] UI improvements --- frontend/src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c8fc9fd..31136c6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -258,7 +258,7 @@ export default function App() { const startPolling = (id: string) => { if (pollRef.current) clearInterval(pollRef.current) - const deadline = Date.now() + 60_000 + const deadline = Date.now() + 120_000 pollRef.current = setInterval(async () => { const expired = Date.now() > deadline if (expired) { @@ -272,7 +272,7 @@ export default function App() { setOrderStatus(s) loadBalances() const n = normalizeStatus(s.status) - const isDone = n === 'finalized' || n === 'executed' || n === 'settling' || n === 'settled' || n === 'failed' || n === 'refunded' || !!s.fillTransaction + const isDone = n === 'finalized' || n === 'executed' || n === 'settling' || n === 'settled' || n === 'failed' || n === 'refunded' if (isDone) { clearInterval(pollRef.current); setStep('done') } } catch {} }, 1000) From 1914d575b0dcf389552384b2ad6cf53ddaa0f87e Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 14:47:55 +0100 Subject: [PATCH 29/34] logo --- frontend/index.html | 2 +- frontend/public/celestia-logo.svg | 125 ++++++++++++++++++ frontend/src/App.tsx | 13 +- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/configs/warp-config.yaml-e | 2 +- hyperlane/hyperlane-addresses.json | 29 ++++ hyperlane/hyperlane-cosmosnative.json | 8 ++ .../registry/chains/anvil1/addresses.yaml | 14 ++ .../registry/chains/anvil2/addresses.yaml | 14 ++ .../warp_routes/USDC/warp-config-config.yaml | 19 +++ .../warp_routes/USDC/warp-config-deploy.yaml | 15 +++ 11 files changed, 229 insertions(+), 14 deletions(-) create mode 100644 frontend/public/celestia-logo.svg create mode 100644 hyperlane/hyperlane-addresses.json create mode 100644 hyperlane/hyperlane-cosmosnative.json create mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml create mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/frontend/index.html b/frontend/index.html index f2a9ab5..95ebf99 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + OIF Solver diff --git a/frontend/public/celestia-logo.svg b/frontend/public/celestia-logo.svg new file mode 100644 index 0000000..f2b823f --- /dev/null +++ b/frontend/public/celestia-logo.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 31136c6..720c6da 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -350,11 +350,7 @@ export default function App() { return (
-
- - - -
+ Celestia
Connecting to solver…
@@ -390,12 +386,7 @@ export default function App() { {/* Logo */}
-
- - - -
+ Celestia
OIF Solver diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/configs/warp-config.yaml-e b/hyperlane/configs/warp-config.yaml-e index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml-e +++ b/hyperlane/configs/warp-config.yaml-e @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json new file mode 100644 index 0000000..64f24e3 --- /dev/null +++ b/hyperlane/hyperlane-addresses.json @@ -0,0 +1,29 @@ +{ + "anvil1": { + "chain_id": 31337, + "domain_id": 131337, + "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "warp_token_type": "collateral", + "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "anvil2": { + "chain_id": 31338, + "domain_id": 31338, + "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "warp_token_type": "synthetic", + "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + }, + "celestiadev": { + "domain_id": 69420, + "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" + } +} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json new file mode 100644 index 0000000..ae2a37b --- /dev/null +++ b/hyperlane/hyperlane-cosmosnative.json @@ -0,0 +1,8 @@ +{ + "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", + "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", + "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" +} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml new file mode 100644 index 0000000..86b298a --- /dev/null +++ b/hyperlane/registry/chains/anvil1/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" +merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" +validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml new file mode 100644 index 0000000..ad2a018 --- /dev/null +++ b/hyperlane/registry/chains/anvil2/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" +proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml new file mode 100644 index 0000000..4ad5a1b --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../schema.json +tokens: + - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + chainName: anvil1 + collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + connections: + - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 + decimals: 6 + name: USDC + standard: EvmHypCollateral + symbol: USDC + - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + chainName: anvil2 + connections: + - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 + decimals: 6 + name: USDC + standard: EvmHypSynthetic + symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml new file mode 100644 index 0000000..dec3a6a --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml @@ -0,0 +1,15 @@ +anvil1: + decimals: 6 + mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + type: collateral +anvil2: + decimals: 6 + mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + type: synthetic From d8a5ae551975a782cc9e3828b8a7b904680f6093 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 14:48:34 +0100 Subject: [PATCH 30/34] clean --- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 ------------------- hyperlane/hyperlane-cosmosnative.json | 8 ----- .../registry/chains/anvil1/addresses.yaml | 14 --------- .../registry/chains/anvil2/addresses.yaml | 14 --------- .../warp_routes/USDC/warp-config-config.yaml | 19 ------------ .../warp_routes/USDC/warp-config-deploy.yaml | 15 ---------- 7 files changed, 1 insertion(+), 100 deletions(-) delete mode 100644 hyperlane/hyperlane-addresses.json delete mode 100644 hyperlane/hyperlane-cosmosnative.json delete mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml delete mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json deleted file mode 100644 index 64f24e3..0000000 --- a/hyperlane/hyperlane-addresses.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "anvil1": { - "chain_id": 31337, - "domain_id": 131337, - "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", - "warp_token_type": "collateral", - "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" - }, - "anvil2": { - "chain_id": 31338, - "domain_id": 31338, - "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "warp_token_type": "synthetic", - "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" - }, - "celestiadev": { - "domain_id": 69420, - "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", - "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" - } -} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json deleted file mode 100644 index ae2a37b..0000000 --- a/hyperlane/hyperlane-cosmosnative.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", - "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", - "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" -} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml deleted file mode 100644 index 86b298a..0000000 --- a/hyperlane/registry/chains/anvil1/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" -merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" -testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" -validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml deleted file mode 100644 index ad2a018..0000000 --- a/hyperlane/registry/chains/anvil2/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" -proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" -staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml deleted file mode 100644 index 4ad5a1b..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# yaml-language-server: $schema=../schema.json -tokens: - - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" - chainName: anvil1 - collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - connections: - - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 - decimals: 6 - name: USDC - standard: EvmHypCollateral - symbol: USDC - - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" - chainName: anvil2 - connections: - - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 - decimals: 6 - name: USDC - standard: EvmHypSynthetic - symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml deleted file mode 100644 index dec3a6a..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml +++ /dev/null @@ -1,15 +0,0 @@ -anvil1: - decimals: 6 - mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - type: collateral -anvil2: - decimals: 6 - mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - type: synthetic From d93248e137978f046b48e27a8ff8c4b811b3f52e Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 15:03:15 +0100 Subject: [PATCH 31/34] gas limit --- frontend/src/App.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 720c6da..3084471 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -323,6 +323,7 @@ export default function App() { args: [prep.celestiaDomainId, prep.forwardingAddressBytes32 as `0x${string}`, BigInt(rawAmount)], chainId: fromId, value: 0n, + gas: 3_000_000n, }) setSlowMsg(`Submitted (${txHash.slice(0, 10)}…). Tokens arrive in ~2 min via Celestia.`) From 41e7b17a629fd5bdd8565bee2738ff440bf225e9 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 15:52:52 +0100 Subject: [PATCH 32/34] add docs page --- frontend/index.html | 2 +- frontend/src/App.tsx | 139 +++-- frontend/src/Docs.tsx | 495 ++++++++++++++++++ frontend/src/wallet.ts | 2 +- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 + hyperlane/hyperlane-cosmosnative.json | 8 + .../registry/chains/anvil1/addresses.yaml | 14 + .../registry/chains/anvil2/addresses.yaml | 14 + .../warp_routes/USDC/warp-config-config.yaml | 19 + .../warp_routes/USDC/warp-config-deploy.yaml | 15 + 11 files changed, 692 insertions(+), 47 deletions(-) create mode 100644 frontend/src/Docs.tsx create mode 100644 hyperlane/hyperlane-addresses.json create mode 100644 hyperlane/hyperlane-cosmosnative.json create mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml create mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/frontend/index.html b/frontend/index.html index 95ebf99..ea94a2d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - OIF Solver + Celestia Bridge diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3084471..a78d289 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,6 +3,7 @@ import { useAppKit, AppKitNetworkButton } from '@reown/appkit/react' import { useAccount, useDisconnect, useWriteContract, useSignTypedData, useSwitchChain } from 'wagmi' import { parseAbi } from 'viem' import { api, Config, AllBalances, Quote, OrderStatus } from './api' +import { Docs } from './Docs' // ── Utilities ───────────────────────────────────────────────────────────────── @@ -134,6 +135,7 @@ export default function App() { const [timedOut, setTimedOut] = useState(false) const [rightTab, setRightTab] = useState<'balances' | 'tools'>('balances') + const [showDocs, setShowDocs] = useState(false) const pollRef = useRef>() @@ -381,42 +383,89 @@ export default function App() {
{/* ── Header ─────────────────────────────────────────────────────────── */} -
-
- +
+ + {/* ── Centered pill navbar ── */} + + + {/* ── Wallet — pinned right ── */} +
{isConnected ? ( -
+ <> -
+
-
+ ) : ( @@ -494,14 +543,14 @@ export default function App() { {/* You Send */}
- You send + You send {balances && fromChain && balances[fromChain] && asset && ( + ))} + + + {/* Mobile tabs */} +
+ {NAV.map(s => ( + + ))} +
+ + {/* Scrollable content */} +
+
+ +
+
Introduction
+

What is Celestia Bridge?

+

+ Celestia Bridge is a cross-chain token transfer interface built on the{' '} + Open Intents Framework, an open protocol for + expressing and fulfilling cross-chain user intents across EVM networks. Move tokens between chains + without managing bridges, liquidity pools, or multi-step transaction sequences yourself. +

+

+ Two routing mechanisms let you choose between speed and trustlessness depending on your needs. +

+ +
+ {[ + { + icon: ( + + + + ), + title: 'Intent-based', + desc: 'Declare what you want. Solvers compete to fill your order at the best available rate.', + }, + { + icon: ( + + + + + + ), + title: 'DA-secured', + desc: 'Backed by Celestia data availability for verifiable, trust-minimized cross-chain messaging.', + }, + { + icon: ( + + + + ), + title: 'Multi-chain', + desc: 'Any EVM chain to any EVM chain. Routes are configured, not hardcoded to specific pairs.', + }, + ].map(f => ( +
+
{f.icon}
+
{f.title}
+
{f.desc}
+
+ ))} +
+ + {/* Architecture overview */} +
+
System overview
+
+ {[ + { label: 'You', sub: 'sign intent', color: 'bg-surface-3 border-border text-gray-300' }, + null, + { label: 'Aggregator', sub: 'routes & quotes', color: 'bg-brand/10 border-brand/25 text-brand-light' }, + null, + { label: 'Solver', sub: 'fills on dest', color: 'bg-surface-3 border-border text-gray-300' }, + null, + { label: 'Oracle', sub: 'attests fill', color: 'bg-surface-3 border-border text-gray-300' }, + ].map((item, i) => + item === null ? ( +
+ + + +
+ ) : ( +
+
{item.label}
+
{item.sub}
+
+ ) + )} +
+
+
+ + + +
+
+ + + Fast Route + + ~15–30 seconds +
+

Intent Protocol

+

+ The fast route uses an intent-based protocol where + specialized solvers compete to fill your transfer. Instead of executing a bridge transaction yourself, + you sign a declaration of what you want; a solver fronts the capital to make it happen + immediately, then settles with the escrow contract after an oracle verifies the fill. +

+

+ You never pay gas. The solver earns a small spread between the input and output amounts as compensation + for the capital risk they take. +

+ +
Step by step
+ + + + + + + + EIP-712 signing separates authorization from execution. You declare intent; solvers execute. + This lets solvers batch, optimize, and compete without requiring you to manage gas or bridge contracts. + Your signature is only valid for the exact parameters you agreed to: amount, destination, expiry. + + + + The oracle operator and solver use different keys and are designed to be separate entities. + A solver attesting its own fills would be "trust me, I did the work" with no independent verification. + The oracle independently confirms fills happened on-chain before any settlement is possible. + +
+ + + +
+
+ + + Slow Route + + ~2 minutes +
+

Celestia Bridge

+

+ The slow route bridges tokens through{' '} + Hyperlane warp routes with cross-chain + messages secured by{' '} + Celestia data availability. + No solvers are involved. The transfer is fully permissionless: no third party can + decline or censor it. +

+

+ The "slow" is Celestia's data availability finality time, not any manual step you have to take. + You send one (or two) transactions and wait ~2 minutes. +

+ +
Step by step
+ + + + + + + + Whether you see an Approve step depends on the{' '} + warpType{' '} + of the source chain. A collateral chain (like Sepolia) + wraps a real ERC-20 and needs allowance. A synthetic chain + (like Eden) owns its token supply and burns from your balance directly, no approval needed. + + + + Celestia provides data availability as a separate layer from execution. By posting cross-chain + messages to Celestia, Hyperlane warp routes inherit Celestia's security for message verification, + independent of Ethereum's or any destination chain's own consensus. + +
+ + + +
+
Routes
+

Choosing the Right Route

+

+ Both routes move the same tokens to the same destination. The difference is the trust model, + speed, and who executes the transfer on your behalf. +

+ + {/* Comparison table */} +
+
+
Feature
+
⚡ Fast
+
〜 Slow
+
+ {[ + ['Speed', '~15–30 seconds', '~2 minutes'], + ['Gas you pay', 'None', 'Source chain tx fee'], + ['Steps', 'Sign once', 'Approve + send (or just send)'], + ['Requires solver', 'Yes', 'No'], + ['Trust model', 'Oracle + solver network', 'Celestia DA + Hyperlane'], + ['Censorship risk', 'Solver can decline', 'Fully permissionless'], + ['Best for', 'Speed, best rate', 'Trustlessness, sovereignty'], + ].map(([feat, fast, slow], i) => ( +
+
{feat}
+
{fast}
+
{slow}
+
+ ))} +
+ + {/* When to use each */} +
+
+
+ + Use Fast when… +
+
    + {[ + 'You want tokens to arrive in seconds', + 'You prefer not to pay gas yourself', + 'Solvers are online and competing', + 'Rate optimization matters', + ].map(item => ( +
  • + + {item} +
  • + ))} +
+
+
+
+ + Use Slow when… +
+
    + {[ + 'You want no solver dependency', + 'Solvers are offline or at capacity', + 'Censorship resistance matters', + 'You prefer Celestia-backed security', + ].map(item => ( +
  • + + {item} +
  • + ))} +
+
+
+
+ +
+
+
+
+ ) +} diff --git a/frontend/src/wallet.ts b/frontend/src/wallet.ts index 106fe44..9fc0376 100644 --- a/frontend/src/wallet.ts +++ b/frontend/src/wallet.ts @@ -61,7 +61,7 @@ export async function initWallet() { networks, projectId, metadata: { - name: 'OIF Solver', + name: 'Celestia Bridge', description: 'Cross-chain solver UI', url: 'http://localhost:5173', icons: [], diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json new file mode 100644 index 0000000..64f24e3 --- /dev/null +++ b/hyperlane/hyperlane-addresses.json @@ -0,0 +1,29 @@ +{ + "anvil1": { + "chain_id": 31337, + "domain_id": 131337, + "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "warp_token_type": "collateral", + "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "anvil2": { + "chain_id": 31338, + "domain_id": 31338, + "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "warp_token_type": "synthetic", + "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + }, + "celestiadev": { + "domain_id": 69420, + "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" + } +} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json new file mode 100644 index 0000000..ae2a37b --- /dev/null +++ b/hyperlane/hyperlane-cosmosnative.json @@ -0,0 +1,8 @@ +{ + "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", + "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", + "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" +} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml new file mode 100644 index 0000000..86b298a --- /dev/null +++ b/hyperlane/registry/chains/anvil1/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" +merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" +validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml new file mode 100644 index 0000000..ad2a018 --- /dev/null +++ b/hyperlane/registry/chains/anvil2/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" +proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml new file mode 100644 index 0000000..4ad5a1b --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../schema.json +tokens: + - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + chainName: anvil1 + collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + connections: + - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 + decimals: 6 + name: USDC + standard: EvmHypCollateral + symbol: USDC + - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + chainName: anvil2 + connections: + - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 + decimals: 6 + name: USDC + standard: EvmHypSynthetic + symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml new file mode 100644 index 0000000..dec3a6a --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml @@ -0,0 +1,15 @@ +anvil1: + decimals: 6 + mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + type: collateral +anvil2: + decimals: 6 + mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + type: synthetic From 693b2035e52ac45e8afd119c6d1f3622e4c67b56 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 12 Mar 2026 17:52:35 +0100 Subject: [PATCH 33/34] add faucets --- frontend/src/App.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a78d289..7a96876 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -950,6 +950,30 @@ export default function App() { Loading…
)} + + {/* Faucet links */} +
+ {[ + { label: 'Eden USDC', sub: 'Testnet USDC faucet', href: 'http://51.159.182.223:8080/' }, + { label: 'Eden Gas', sub: 'Testnet ETH faucet', href: 'https://faucet-eden-testnet.binarybuilders.services/' }, + ].map(({ label, sub, href }) => ( + +
+
{label}
+
{sub}
+
+ + + +
+ ))} +
)} From e768fda0f8a6158164a4701807da8b2628c17a06 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Fri, 13 Mar 2026 15:18:21 +0100 Subject: [PATCH 34/34] remove default user from frontend --- .continue/config.yaml | 17 ++ frontend/server.js | 210 ++---------------- frontend/src/App.tsx | 129 +++++------ frontend/src/api.ts | 14 -- hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 --- hyperlane/hyperlane-cosmosnative.json | 8 - .../registry/chains/anvil1/addresses.yaml | 14 -- .../registry/chains/anvil2/addresses.yaml | 14 -- .../warp_routes/USDC/warp-config-config.yaml | 19 -- .../warp_routes/USDC/warp-config-deploy.yaml | 15 -- solver-cli/src/chain/contracts.rs | 1 + 12 files changed, 95 insertions(+), 377 deletions(-) create mode 100644 .continue/config.yaml delete mode 100644 hyperlane/hyperlane-addresses.json delete mode 100644 hyperlane/hyperlane-cosmosnative.json delete mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml delete mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml delete mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/.continue/config.yaml b/.continue/config.yaml new file mode 100644 index 0000000..f313691 --- /dev/null +++ b/.continue/config.yaml @@ -0,0 +1,17 @@ +models: + - name: DeepSeek Chat + provider: ollama + model: deepseek-coder:33b + roles: + - chat + - edit + + - name: DeepSeek Fast + provider: ollama + model: deepseek-coder:6.7b + roles: + - autocomplete + +embeddings: + provider: ollama + model: nomic-embed-text \ No newline at end of file diff --git a/frontend/server.js b/frontend/server.js index a64b8e1..df3eb9a 100644 --- a/frontend/server.js +++ b/frontend/server.js @@ -21,12 +21,6 @@ function readState() { return JSON.parse(readFileSync(resolve(ROOT, '.config/state.json'), 'utf8')); } -function getUserAccount() { - const pk = process.env.USER_PK; - if (!pk) throw new Error('USER_PK not set in .env'); - return privateKeyToAccount(`0x${pk.replace('0x', '')}`); -} - function makeViemChain(chainId, name, rpc) { return defineChain({ id: chainId, @@ -62,11 +56,6 @@ const erc20Abi = parseAbi([ 'function approve(address, uint256) returns (bool)', ]); -const hypTokenAbi = parseAbi([ - 'function transferRemote(uint32, bytes32, uint256) payable returns (bytes32)', - 'function balanceOf(address) view returns (uint256)', -]); - // ── Bech32 → bytes32 (replaces Python script) ─────────────────────────────── const BECH32_CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; @@ -262,7 +251,6 @@ app.get('/api/health', async (_req, res) => { app.get('/api/config', (_req, res) => { try { const state = readState(); - const user = getUserAccount(); const chains = {}; // Build warp route fallback tokens (same logic as /api/balances) @@ -300,7 +288,6 @@ app.get('/api/config', (_req, res) => { res.json({ chains, - userAddress: user.address, solverAddress: state.solver?.address ?? null, faucetChains, }); @@ -313,14 +300,11 @@ app.get('/api/config', (_req, res) => { app.get('/api/balances', async (req, res) => { try { const state = readState(); - // Use connected wallet address if provided, otherwise fall back to USER_PK const addressParam = req.query.address; - let userAddress; - if (addressParam && /^0x[0-9a-fA-F]{40}$/.test(addressParam)) { - userAddress = addressParam; - } else { - userAddress = getUserAccount().address; + if (!addressParam || !/^0x[0-9a-fA-F]{40}$/.test(addressParam)) { + return res.status(400).json({ error: 'Missing or invalid "address" query parameter. Connect a wallet.' }); } + const userAddress = addressParam; console.log(`[balances] user=${userAddress} solver=${state.solver?.address}`); const result = {}; @@ -400,16 +384,10 @@ app.post('/api/faucet', async (req, res) => { const chain = Object.values(state.chains).find(c => c.name === chainName); if (!chain) throw new Error(`Chain "${chainName}" not found`); - // Use provided address (from connected wallet) or fall back to USER_PK - let recipient; - if (address) { - if (!/^0x[0-9a-fA-F]{40}$/.test(address)) { - throw new Error('Invalid Ethereum address'); - } - recipient = address; - } else { - recipient = getUserAccount().address; + if (!address || !/^0x[0-9a-fA-F]{40}$/.test(address)) { + throw new Error('Missing or invalid "address". Connect a wallet first.'); } + const recipient = address; // Resolve deployer key const envKey = `${chainName.toUpperCase()}_PK`; @@ -520,86 +498,9 @@ app.post('/api/bridge/prepare', async (req, res) => { } }); -// Bridge: server-side user bridge via Celestia (uses USER_PK) -app.post('/api/bridge', async (req, res) => { - const { from, to, amount = '10000000', token = 'USDC' } = req.body; - if (!from || !to) return res.status(400).json({ error: '"from" and "to" are required' }); - try { - const warp = getWarpRouteConfig(token); - const src = warp.chains[from]; - const dst = warp.chains[to]; - if (!src) throw new Error(`Chain "${from}" not found in ${token} warp route`); - if (!dst) throw new Error(`Chain "${to}" not found in ${token} warp route`); - - const underlyingToApprove = src.warpType === 'collateral' ? src.underlying : null; - const dstBalanceToken = dst.warpType === 'collateral' ? dst.underlying : dst.warpToken; - - const userPk = process.env.USER_PK; - if (!userPk) throw new Error('USER_PK not set in .env'); - const user = privateKeyToAccount(`0x${userPk.replace('0x', '')}`); - - const srcChain = makeViemChain(src.chainId, from, src.rpc); - const dstChain = makeViemChain(dst.chainId, to, dst.rpc); - const srcWallet = createWalletClient({ account: user, chain: srcChain, transport: http(src.rpc) }); - const srcPublic = createPublicClient({ chain: srcChain, transport: http(src.rpc) }); - const dstPublic = createPublicClient({ chain: dstChain, transport: http(dst.rpc) }); - - const userPadded = '0x000000000000000000000000' + user.address.slice(2); - const amountBigInt = BigInt(amount); - - const forwardingBackend = process.env.FORWARDING_BACKEND || 'http://127.0.0.1:8080'; - const addrResp = await fetch( - `${forwardingBackend}/forwarding-address?dest_domain=${dst.domainId}&dest_recipient=${userPadded}` - ); - if (!addrResp.ok) throw new Error(`Failed to derive forwarding address: ${await addrResp.text()}`); - const { address: forwardAddr } = await addrResp.json(); - - const registerResp = await fetch(`${forwardingBackend}/forwarding-requests`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ forward_addr: forwardAddr, dest_domain: dst.domainId, dest_recipient: userPadded }), - }); - if (!registerResp.ok) throw new Error(`Forwarding registration failed: ${await registerResp.text()}`); - - const initialDstBal = await dstPublic.readContract({ - address: dstBalanceToken, abi: hypTokenAbi, functionName: 'balanceOf', args: [user.address], - }); - - if (underlyingToApprove) { - const approveHash = await srcWallet.writeContract({ - address: underlyingToApprove, abi: erc20Abi, - functionName: 'approve', args: [src.warpToken, amountBigInt], - }); - await srcPublic.waitForTransactionReceipt({ hash: approveHash }); - } - - const txHash = await srcWallet.writeContract({ - address: src.warpToken, abi: hypTokenAbi, - functionName: 'transferRemote', - args: [warp.celestiaDomainId, bech32ToBytes32(forwardAddr), amountBigInt], - value: 0n, - }); - await srcPublic.waitForTransactionReceipt({ hash: txHash }); - - let arrived = false; - for (let i = 1; i <= 12; i++) { - await new Promise(r => setTimeout(r, 5000)); - const bal = await dstPublic.readContract({ - address: dstBalanceToken, abi: hypTokenAbi, functionName: 'balanceOf', args: [user.address], - }); - if (bal !== initialDstBal && bal > 0n) { arrived = true; break; } - } - - res.json({ - success: arrived, - message: arrived - ? `Bridged ${token} from ${from} to ${to}` - : `Bridge tx sent but tokens haven't arrived yet. Check Hyperlane relayer.`, - txHash, - }); - } catch (err) { - res.status(500).json({ error: `Bridge failed: ${err.message}` }); - } +// Bridge: server-side signing removed — wallet handles tx submission via /api/bridge/prepare +app.post('/api/bridge', (_req, res) => { + res.status(400).json({ error: 'Server-side bridge removed. Connect a wallet and use the wallet-based bridge flow.' }); }); // Quote: request quote from aggregator @@ -607,13 +508,10 @@ app.post('/api/quote', async (req, res) => { const { fromChainId, toChainId, amount, asset = 'USDC', address } = req.body; try { const state = readState(); - const user = getUserAccount(); - - // Use connected wallet address if provided, otherwise fall back to USER_PK - let userAddress = user.address; - if (address && /^0x[0-9a-fA-F]{40}$/.test(address)) { - userAddress = address; + if (!address || !/^0x[0-9a-fA-F]{40}$/.test(address)) { + throw new Error('Missing or invalid "address". Connect a wallet first.'); } + const userAddress = address; const fromChain = state.chains[fromChainId.toString()]; const toChain = state.chains[toChainId.toString()]; @@ -665,87 +563,9 @@ app.post('/api/quote', async (req, res) => { } }); -// Order: approve tokens, sign EIP-712, prepend type byte, submit to aggregator -app.post('/api/order', async (req, res) => { - const { quote, fromChainId, asset = 'USDC' } = req.body; - try { - const user = getUserAccount(); - const state = readState(); - - // Resolve source chain for approval + signing - const srcChainId = fromChainId?.toString() || Object.keys(state.chains)[0]; - const srcChain = state.chains[srcChainId]; - if (!srcChain) throw new Error(`Source chain ${srcChainId} not found`); - - const viemChain = makeViemChain(srcChain.chain_id, srcChain.name, srcChain.rpc); - - const walletClient = createWalletClient({ - account: user, - chain: viemChain, - transport: http(srcChain.rpc), - }); - const publicClient = createPublicClient({ - chain: viemChain, - transport: http(srcChain.rpc), - }); - - // Step 1: Approve token spending - // For Permit2: approve the Permit2 contract; for direct: approve the escrow - const token = srcChain.tokens[asset]; - const payload = quote.order.payload; - const isPermit2Approval = payload.primaryType?.includes('Permit'); - const spender = isPermit2Approval - ? payload.domain?.verifyingContract // Permit2 contract - : srcChain.contracts?.input_settler_escrow; - if (token && spender) { - const approveHash = await walletClient.writeContract({ - address: token.address, - abi: parseAbi(['function approve(address, uint256) returns (bool)']), - functionName: 'approve', - args: [spender, 100000000n], // 100 USDC allowance - }); - await publicClient.waitForTransactionReceipt({ hash: approveHash }); - } - - // Step 2: Sign EIP-712 typed data with viem - // (payload already extracted above for approval target) - - // Remove EIP712Domain from types (viem adds it automatically) - const types = { ...payload.types }; - delete types.EIP712Domain; - - // Coerce domain.chainId to number — the aggregator returns it as a string - // which causes viem to produce a different EIP-712 hash - const domain = { ...payload.domain }; - if (typeof domain.chainId === 'string') { - domain.chainId = Number(domain.chainId); - } - - const rawSignature = await user.signTypedData({ - domain, - types, - primaryType: payload.primaryType, - message: payload.message, - }); - - // Prepend signature type byte: 0x00=Permit2, 0x01=EIP-3009, 0xff=self - const isPermit2 = payload.primaryType?.includes('Permit'); - const prefix = isPermit2 ? '0x00' : '0x01'; - const signature = prefix + rawSignature.slice(2); - - // Step 4: Submit signed order to aggregator - const response = await fetch(`${AGGREGATOR_URL}/api/v1/orders`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ quoteResponse: quote, signature }), - }); - - const data = await response.json(); - if (!response.ok) throw new Error(data.message || data.error || JSON.stringify(data)); - res.json(data); - } catch (err) { - res.status(500).json({ error: err.message }); - } +// Order: deprecated server-side signing — use /api/order/submit with wallet signature +app.post('/api/order', (_req, res) => { + res.status(400).json({ error: 'Server-side signing removed. Connect a wallet and use the client-side signing flow.' }); }); // Order submit: forward a pre-signed order (for MetaMask / client-side signing flow) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7a96876..1a66fcc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -169,9 +169,9 @@ export default function App() { }, []) const loadBalances = useCallback(async () => { + if (!isConnected || !connectedAddress) { setBalances(null); return } try { - const addr = isConnected && connectedAddress ? connectedAddress : undefined - setBalances(await api.balances(addr)) + setBalances(await api.balances(connectedAddress)) } catch {} }, [isConnected, connectedAddress]) @@ -192,6 +192,7 @@ export default function App() { const handleGetQuote = async () => { if (!fromChain || !toChain || !amount) return + if (!isConnected || !connectedAddress) { setError('Connect a wallet first.'); setStep('error'); return } setStep('quoting'); setError(''); setQuote(null); setOrderId(''); setOrderStatus(null) try { const fromId = config!.chains[fromChain].chainId @@ -199,8 +200,7 @@ export default function App() { const raw = Math.round(parseFloat(amount) * 10 ** selectedTokenDecimals).toString() let resp: any try { - resp = await api.quote(fromId, toId, raw, asset, - isConnected && connectedAddress ? connectedAddress : undefined) + resp = await api.quote(fromId, toId, raw, asset, connectedAddress) } catch (fetchErr: any) { const msg: string = fetchErr?.message ?? '' // Only treat as network error if msg hasn't already been categorized by server.js @@ -224,37 +224,33 @@ export default function App() { const handleAcceptQuote = async () => { if (!quote) return + if (!isConnected || !connectedAddress) { setError('Connect a wallet first.'); setStep('error'); return } setStep('signing'); setError('') try { const fromId = config!.chains[fromChain].chainId - if (isConnected && connectedAddress) { - const chainInfo = config!.chains[fromChain] - const token = chainInfo.tokens[asset] - await switchChainAsync({ chainId: fromId }) - const payload = quote.order.payload as any - const isPermit2 = payload.primaryType?.includes('Permit') - const spender = isPermit2 - ? (payload.domain?.verifyingContract as `0x${string}`) - : (chainInfo.contracts?.input_settler_escrow as `0x${string}`) - if (token && spender) { - await writeContractAsync({ - address: token.address as `0x${string}`, - abi: parseAbi(['function approve(address, uint256) returns (bool)']), - functionName: 'approve', args: [spender, BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')], chainId: fromId, - }) - } - const types = { ...payload.types } - delete types.EIP712Domain - const domain = { ...payload.domain } - if (typeof domain.chainId === 'string') domain.chainId = Number(domain.chainId) - const rawSig = await signTypedDataAsync({ domain, types, primaryType: payload.primaryType, message: payload.message }) - const sig = (isPermit2 ? '0x00' : '0x01') + rawSig.slice(2) - const resp = await api.submitSignedOrder(quote, sig) - setOrderId(resp.orderId); setStep('polling'); startPolling(resp.orderId) - } else { - const resp = await api.submitOrder(quote, config!.chains[fromChain].chainId, asset) - setOrderId(resp.orderId); setStep('polling'); startPolling(resp.orderId) + const chainInfo = config!.chains[fromChain] + const token = chainInfo.tokens[asset] + await switchChainAsync({ chainId: fromId }) + const payload = quote.order.payload as any + const isPermit2 = payload.primaryType?.includes('Permit') + const spender = isPermit2 + ? (payload.domain?.verifyingContract as `0x${string}`) + : (chainInfo.contracts?.input_settler_escrow as `0x${string}`) + if (token && spender) { + await writeContractAsync({ + address: token.address as `0x${string}`, + abi: parseAbi(['function approve(address, uint256) returns (bool)']), + functionName: 'approve', args: [spender, BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff')], chainId: fromId, + }) } + const types = { ...payload.types } + delete types.EIP712Domain + const domain = { ...payload.domain } + if (typeof domain.chainId === 'string') domain.chainId = Number(domain.chainId) + const rawSig = await signTypedDataAsync({ domain, types, primaryType: payload.primaryType, message: payload.message }) + const sig = (isPermit2 ? '0x00' : '0x01') + rawSig.slice(2) + const resp = await api.submitSignedOrder(quote, sig) + setOrderId(resp.orderId); setStep('polling'); startPolling(resp.orderId) } catch (err: any) { setError(err.message); setStep('error') } } @@ -299,43 +295,40 @@ export default function App() { const fromId = config!.chains[fromChain].chainId const rawAmount = Math.round(parseFloat(amount) * 10 ** selectedTokenDecimals).toString() - if (isConnected && connectedAddress) { - // Wallet-connected: server prepares the forwarding, wallet executes the txs - setSlowMsg('Preparing Celestia route…') - const prep = await api.bridgePrepare(fromName, toName, asset, connectedAddress, rawAmount) - - await switchChainAsync({ chainId: fromId }) - - if (prep.needsApproval && prep.underlyingToken) { - setSlowMsg('Approving token…') - await writeContractAsync({ - address: prep.underlyingToken as `0x${string}`, - abi: parseAbi(['function approve(address, uint256) returns (bool)']), - functionName: 'approve', - args: [prep.warpToken as `0x${string}`, BigInt(rawAmount)], - chainId: fromId, - }) - } + if (!isConnected || !connectedAddress) { + throw new Error('Connect a wallet to use the bridge.') + } + + // Wallet-connected: server prepares the forwarding, wallet executes the txs + setSlowMsg('Preparing Celestia route…') + const prep = await api.bridgePrepare(fromName, toName, asset, connectedAddress, rawAmount) - setSlowMsg('Submitting bridge transaction…') - const txHash = await writeContractAsync({ - address: prep.warpToken as `0x${string}`, - abi: parseAbi(['function transferRemote(uint32, bytes32, uint256) payable returns (bytes32)']), - functionName: 'transferRemote', - args: [prep.celestiaDomainId, prep.forwardingAddressBytes32 as `0x${string}`, BigInt(rawAmount)], + await switchChainAsync({ chainId: fromId }) + + if (prep.needsApproval && prep.underlyingToken) { + setSlowMsg('Approving token…') + await writeContractAsync({ + address: prep.underlyingToken as `0x${string}`, + abi: parseAbi(['function approve(address, uint256) returns (bool)']), + functionName: 'approve', + args: [prep.warpToken as `0x${string}`, BigInt(rawAmount)], chainId: fromId, - value: 0n, - gas: 3_000_000n, }) - - setSlowMsg(`Submitted (${txHash.slice(0, 10)}…). Tokens arrive in ~2 min via Celestia.`) - loadBalances() - } else { - // No wallet: server executes using USER_PK - const resp = await api.bridge(fromName, toName, rawAmount, asset) - setSlowMsg(resp.message) - loadBalances() } + + setSlowMsg('Submitting bridge transaction…') + const txHash = await writeContractAsync({ + address: prep.warpToken as `0x${string}`, + abi: parseAbi(['function transferRemote(uint32, bytes32, uint256) payable returns (bytes32)']), + functionName: 'transferRemote', + args: [prep.celestiaDomainId, prep.forwardingAddressBytes32 as `0x${string}`, BigInt(rawAmount)], + chainId: fromId, + value: 0n, + gas: 3_000_000n, + }) + + setSlowMsg(`Submitted (${txHash.slice(0, 10)}…). Tokens arrive in ~2 min via Celestia.`) + loadBalances() } catch (err: any) { setSlowMsg(`Error: ${err.message}`) } finally { @@ -904,7 +897,7 @@ export default function App() {
You - {(connectedAddress || config?.userAddress) && } + {connectedAddress && }
{asset && cb.balances.user[asset] && ( @@ -985,10 +978,10 @@ export default function App() {
System
- {config?.userAddress && ( + {connectedAddress && (
- User - + Wallet +
)} {config?.solverAddress && ( diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 86a2958..02cdbcf 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -16,7 +16,6 @@ export interface ChainInfo { export interface Config { chains: Record - userAddress: string solverAddress: string } @@ -114,13 +113,6 @@ export const api = { body: JSON.stringify({ fromChainId, toChainId, amount, asset, ...(address ? { address } : {}) }), }), - submitOrder: (quote: Quote, fromChainId: number, asset: string, address?: string) => - json(`${BASE}/order`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ quote, fromChainId, asset, ...(address ? { address } : {}) }), - }), - submitSignedOrder: (quote: Quote, signature: string) => json(`${BASE}/order/submit`, { method: 'POST', @@ -143,10 +135,4 @@ export const api = { body: JSON.stringify({ from, to, token, address, amount }), }), - bridge: (from: string, to: string, amount: string, token?: string) => - json<{ success: boolean; message: string; txHash?: string }>(`${BASE}/bridge`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ from, to, amount, ...(token ? { token } : {}) }), - }), } diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index c71dc75..2828ae2 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + token: "MOCK_USDC_ADDRESS_PLACEHOLDER" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json deleted file mode 100644 index 64f24e3..0000000 --- a/hyperlane/hyperlane-addresses.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "anvil1": { - "chain_id": 31337, - "domain_id": 131337, - "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", - "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", - "warp_token_type": "collateral", - "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", - "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", - "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" - }, - "anvil2": { - "chain_id": 31338, - "domain_id": 31338, - "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", - "warp_token_type": "synthetic", - "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", - "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", - "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" - }, - "celestiadev": { - "domain_id": 69420, - "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", - "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" - } -} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json deleted file mode 100644 index ae2a37b..0000000 --- a/hyperlane/hyperlane-cosmosnative.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", - "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", - "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", - "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", - "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", - "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" -} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml deleted file mode 100644 index 86b298a..0000000 --- a/hyperlane/registry/chains/anvil1/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" -merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" -testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" -validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml deleted file mode 100644 index ad2a018..0000000 --- a/hyperlane/registry/chains/anvil2/addresses.yaml +++ /dev/null @@ -1,14 +0,0 @@ -domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" -incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" -interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" -mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" -merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" -proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" -staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" -staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" -staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" -staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" -testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" -validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml deleted file mode 100644 index 4ad5a1b..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# yaml-language-server: $schema=../schema.json -tokens: - - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" - chainName: anvil1 - collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - connections: - - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 - decimals: 6 - name: USDC - standard: EvmHypCollateral - symbol: USDC - - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" - chainName: anvil2 - connections: - - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 - decimals: 6 - name: USDC - standard: EvmHypSynthetic - symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml deleted file mode 100644 index dec3a6a..0000000 --- a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml +++ /dev/null @@ -1,15 +0,0 @@ -anvil1: - decimals: 6 - mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" - type: collateral -anvil2: - decimals: 6 - mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" - name: USDC - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" - symbol: USDC - type: synthetic diff --git a/solver-cli/src/chain/contracts.rs b/solver-cli/src/chain/contracts.rs index 6d69efb..f0419ad 100644 --- a/solver-cli/src/chain/contracts.rs +++ b/solver-cli/src/chain/contracts.rs @@ -13,6 +13,7 @@ sol! { function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address from, address to, uint256 amount) external returns (bool); + } }