From c505b40acac2cceb6cba13f3b3586b722a9f7405 Mon Sep 17 00:00:00 2001 From: File Large Date: Fri, 26 Sep 2025 16:21:40 +0200 Subject: [PATCH 1/2] feat: rebalancer service --- Cargo.lock | 64 ++--- Cargo.toml | 1 + crates/bid-scraper/Cargo.toml | 4 +- crates/eth-sparse-mpt/Cargo.toml | 2 +- crates/rbuilder-config/src/logger.rs | 15 +- crates/rbuilder-rebalancer/Cargo.toml | 32 +++ crates/rbuilder-rebalancer/src/bin/main.rs | 68 +++++ crates/rbuilder-rebalancer/src/config.rs | 118 ++++++++ crates/rbuilder-rebalancer/src/lib.rs | 2 + crates/rbuilder-rebalancer/src/rebalancer.rs | 279 +++++++++++++++++++ crates/rbuilder/src/utils/tx_signer.rs | 5 +- 11 files changed, 547 insertions(+), 43 deletions(-) create mode 100644 crates/rbuilder-rebalancer/Cargo.toml create mode 100644 crates/rbuilder-rebalancer/src/bin/main.rs create mode 100644 crates/rbuilder-rebalancer/src/config.rs create mode 100644 crates/rbuilder-rebalancer/src/lib.rs create mode 100644 crates/rbuilder-rebalancer/src/rebalancer.rs diff --git a/Cargo.lock b/Cargo.lock index 197d3654c..4d00a3417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2020,7 +2020,7 @@ dependencies = [ "alloy-rpc-types-beacon", "async-trait", "chrono", - "clap 3.2.25", + "clap 4.5.36", "derivative", "ethereum_ssz 0.9.0", "ethereum_ssz_derive", @@ -2700,14 +2700,9 @@ version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ - "atty", "bitflags 1.3.2", - "clap_derive 3.2.25", "clap_lex 0.2.4", "indexmap 1.9.3", - "once_cell", - "strsim 0.10.0", - "termcolor", "textwrap", ] @@ -2718,7 +2713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04" dependencies = [ "clap_builder", - "clap_derive 4.5.32", + "clap_derive", ] [[package]] @@ -2730,20 +2725,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex 0.7.4", - "strsim 0.11.1", -] - -[[package]] -name = "clap_derive" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" -dependencies = [ - "heck 0.4.1", - "proc-macro-error", - "proc-macro2 1.0.95", - "quote 1.0.40", - "syn 1.0.109", + "strsim", ] [[package]] @@ -3343,7 +3325,7 @@ dependencies = [ "ident_case", "proc-macro2 1.0.95", "quote 1.0.40", - "strsim 0.11.1", + "strsim", "syn 2.0.100", ] @@ -9352,6 +9334,29 @@ dependencies = [ "uuid", ] +[[package]] +name = "rbuilder-rebalancer" +version = "0.1.0" +dependencies = [ + "alloy-consensus 1.0.27", + "alloy-eips 1.0.27", + "alloy-primitives 1.3.1", + "alloy-provider", + "alloy-rpc-types-eth 1.0.27", + "alloy-signer", + "alloy-signer-local", + "clap 4.5.36", + "eyre", + "futures", + "rbuilder-config", + "reqwest 0.12.15", + "serde", + "serde_json", + "tokio", + "toml 0.8.20", + "tracing", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -13960,12 +13965,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "strsim" version = "0.11.1" @@ -14273,15 +14272,6 @@ dependencies = [ "utf-8", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "termtree" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 150ceee7b..e1c373ea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "crates/rbuilder-config", "crates/rbuilder", "crates/rbuilder-operator", + "crates/rbuilder-rebalancer", "crates/reth-rbuilder", "crates/rbuilder/src/test_utils", "crates/rbuilder/src/telemetry/metrics_macros", diff --git a/crates/bid-scraper/Cargo.toml b/crates/bid-scraper/Cargo.toml index 7d4f58fda..def219e60 100644 --- a/crates/bid-scraper/Cargo.toml +++ b/crates/bid-scraper/Cargo.toml @@ -20,7 +20,7 @@ tokio-util.workspace = true tracing = "0.1.37" tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] } log = "^0.4" -clap = { version = "^3.2", features = ["derive"] } +clap.workspace = true serde_json = { workspace = true } serde = { workspace = true } serde_with = { version = "3.9.0", features = ["time_0_3"] } @@ -41,7 +41,7 @@ ssz_types = "^0.5" hex = "^0.4" derivative.workspace = true toml.workspace = true -eyre = "0.6.12" +eyre.workspace = true thiserror.workspace = true parking_lot.workspace = true strum = { version = "0.25", features = ["derive"] } diff --git a/crates/eth-sparse-mpt/Cargo.toml b/crates/eth-sparse-mpt/Cargo.toml index b25275e86..26de9cb85 100644 --- a/crates/eth-sparse-mpt/Cargo.toml +++ b/crates/eth-sparse-mpt/Cargo.toml @@ -10,7 +10,7 @@ repository.workspace = true [dependencies] thiserror = "1.0.61" -serde = { version = "1.0.203", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } serde_json = "1.0.117" serde_with = "3.9.0" rustc-hash = "2.0.0" diff --git a/crates/rbuilder-config/src/logger.rs b/crates/rbuilder-config/src/logger.rs index 867ace4c3..bfbfeb377 100644 --- a/crates/rbuilder-config/src/logger.rs +++ b/crates/rbuilder-config/src/logger.rs @@ -1,13 +1,26 @@ use tracing_subscriber::EnvFilter; /// Logger configuration. -#[derive(Debug, Clone)] +#[derive(PartialEq, Eq, Clone, Debug, serde::Deserialize)] pub struct LoggerConfig { pub env_filter: String, + #[serde(default)] pub log_json: bool, + #[serde(default)] pub log_color: bool, } +impl LoggerConfig { + /// Default logger configuration for development. + pub fn dev() -> Self { + Self { + env_filter: String::from("info"), + log_color: true, + log_json: false, + } + } +} + impl LoggerConfig { /// Initialize tracing subscriber based on the configuration. pub fn init_tracing(self) -> eyre::Result<()> { diff --git a/crates/rbuilder-rebalancer/Cargo.toml b/crates/rbuilder-rebalancer/Cargo.toml new file mode 100644 index 000000000..ba6c8d76d --- /dev/null +++ b/crates/rbuilder-rebalancer/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "rbuilder-rebalancer" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +rbuilder-config.workspace = true + +alloy-primitives = { workspace = true, features = ["serde"] } +alloy-eips.workspace = true +alloy-consensus.workspace = true +alloy-rpc-types-eth.workspace = true +alloy-provider.workspace = true +alloy-signer.workspace = true +alloy-signer-local.workspace = true + +# rt +tokio = { workspace = true, default-features = false, features = ["macros", "rt-multi-thread"] } +futures.workspace = true + +# misc +serde = { workspace = true, features = ["derive"] } +serde_json.workspace = true +clap.workspace = true +eyre.workspace = true +toml.workspace = true +tracing.workspace = true +reqwest.workspace = true diff --git a/crates/rbuilder-rebalancer/src/bin/main.rs b/crates/rbuilder-rebalancer/src/bin/main.rs new file mode 100644 index 000000000..308ef74a5 --- /dev/null +++ b/crates/rbuilder-rebalancer/src/bin/main.rs @@ -0,0 +1,68 @@ +use alloy_provider::ProviderBuilder; +use alloy_signer_local::PrivateKeySigner; +use clap::Parser; +use rbuilder_rebalancer::{config::RebalancerConfig, rebalancer::Rebalancer}; +use std::{path::PathBuf, str::FromStr, time::Duration}; +use tracing::{level_filters::LevelFilter, *}; + +#[tokio::main] +async fn main() { + if let Err(error) = Cli::parse().run().await { + eprintln!("Error: {error:?}"); + std::process::exit(1); + } +} + +#[derive(Parser)] +struct Cli { + #[clap(env = "REBALANCER_CONFIG", help = "Config file path")] + config: PathBuf, +} + +impl Cli { + async fn run(self) -> eyre::Result<()> { + let config = RebalancerConfig::parse_toml_file(&self.config)?; + + config.logger.init_tracing()?; + + if config.rules.is_empty() { + warn!("No rebalancing rules have been configured, rebalancer will be idling"); + } + + for rule in &config.rules { + if rule.destination_min_balance >= rule.destination_target_balance { + eyre::bail!("Invalid configuration for rule `{}`: minimum balance must be lower than the target", rule.description); + } + + if !config.accounts.iter().any(|acc| acc.id == rule.source_id) { + eyre::bail!("Invalid configuration for rule `{}`: account entry is missing for source account {}", rule.description, rule.source_id); + } + } + + let rpc_provider = ProviderBuilder::new().connect(&config.rpc_url).await?; + let transfer_max_priority_fee_per_gas = + config.transfer_max_priority_fee_per_gas.try_into().unwrap(); + let accounts = config + .accounts + .into_iter() + .map(|account| { + let account = account.map_secret(|secret| { + let secret = secret.value().expect("invalid env"); + PrivateKeySigner::from_str(&secret).expect("invalid private key") + }); + (account.id.clone(), account) + }) + .collect(); + Rebalancer::new( + rpc_provider, + config.builder_url, + transfer_max_priority_fee_per_gas, + accounts, + config.rules, + Duration::from_secs(2), + Duration::from_secs(2), + ) + .run() + .await + } +} diff --git a/crates/rbuilder-rebalancer/src/config.rs b/crates/rbuilder-rebalancer/src/config.rs new file mode 100644 index 000000000..7edb6a85e --- /dev/null +++ b/crates/rbuilder-rebalancer/src/config.rs @@ -0,0 +1,118 @@ +use alloy_primitives::{Address, U256}; +use rbuilder_config::{EnvOrValue, LoggerConfig}; +use serde::Deserialize; +use std::{fs, path::Path}; + +#[derive(PartialEq, Eq, Debug, Deserialize)] +pub struct RebalancerConfig { + /// Node RPC URL for block subscription and state fetch. + pub rpc_url: String, + /// Builder RPC URL for bundle submissions. + pub builder_url: String, + /// Max priority fee per to set on the transfer. + pub transfer_max_priority_fee_per_gas: U256, + /// Logger configuration. + #[serde(flatten)] + pub logger: LoggerConfig, + /// Source accounts for funding. + #[serde(default, rename = "account")] + pub accounts: Vec>>, + /// Collection of rebelancer rules. + #[serde(default, rename = "rule")] + pub rules: Vec, +} + +impl RebalancerConfig { + /// Parse toml file. + pub fn parse_toml_file(path: &Path) -> eyre::Result { + let content = fs::read_to_string(path)?; + Ok(Self::parse_toml(&content)?) + } + + /// Parse relay configurations from toml string. + pub fn parse_toml(s: &str) -> Result { + toml::from_str(s) + } +} + +#[derive(PartialEq, Eq, Debug, Deserialize)] +pub struct RebalancerAccount { + /// Account ID. + pub id: String, + /// Account secret. + pub secret: S, + /// Minimum balance for source account. + pub min_balance: U256, +} + +impl RebalancerAccount { + /// Map account secret. + pub fn map_secret(self, map: F) -> RebalancerAccount + where + F: FnOnce(S) -> T, + { + RebalancerAccount { + id: self.id, + secret: map(self.secret), + min_balance: self.min_balance, + } + } +} + +#[derive(PartialEq, Eq, Debug, Deserialize)] +pub struct RebalancerRule { + /// Rule description. + pub description: String, + /// The source of funds referenced by id. + pub source_id: String, + /// Destination address. + pub destination: Address, + /// Destination target balance. + pub destination_target_balance: U256, + /// Destination minimum threshold balance after which rebalancing will be triggered. + pub destination_min_balance: U256, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_config() { + let config = RebalancerConfig::parse_toml( + r#" + rpc_url = "" + builder_url = "" + transfer_max_priority_fee_per_gas = "123" + + env_filter = "info" + log_color = true + + [[rule]] + description = "" + source_id = "" + destination = "0x0000000000000000000000000000000000000000" + destination_target_balance = "1" + destination_min_balance = "1" + "#, + ) + .unwrap(); + assert_eq!( + config, + RebalancerConfig { + rpc_url: String::new(), + builder_url: String::new(), + transfer_max_priority_fee_per_gas: U256::from(123), + logger: LoggerConfig::dev(), + accounts: Vec::new(), + rules: Vec::from([RebalancerRule { + description: String::new(), + source_id: String::new(), + destination: Address::ZERO, + destination_min_balance: U256::from(1), + destination_target_balance: U256::from(1), + }]) + } + ); + } +} diff --git a/crates/rbuilder-rebalancer/src/lib.rs b/crates/rbuilder-rebalancer/src/lib.rs new file mode 100644 index 000000000..46170bea8 --- /dev/null +++ b/crates/rbuilder-rebalancer/src/lib.rs @@ -0,0 +1,2 @@ +pub mod config; +pub mod rebalancer; diff --git a/crates/rbuilder-rebalancer/src/rebalancer.rs b/crates/rbuilder-rebalancer/src/rebalancer.rs new file mode 100644 index 000000000..f4bd82c64 --- /dev/null +++ b/crates/rbuilder-rebalancer/src/rebalancer.rs @@ -0,0 +1,279 @@ +use alloy_consensus::{ + EthereumTxEnvelope, EthereumTypedTransaction, SignableTransaction as _, TxEip1559, TxEip4844, +}; +use alloy_eips::{eip1559::BaseFeeParams, BlockId, Encodable2718}; +use alloy_primitives::{ + hex, + map::{AddressMap, AddressSet, HashMap}, + Address, Bytes, TxKind, B256, U256, +}; +use alloy_provider::Provider; +use alloy_rpc_types_eth::{AccountInfo, Header}; +use alloy_signer::SignerSync; +use alloy_signer_local::PrivateKeySigner; +use futures::{stream::FuturesUnordered, FutureExt, StreamExt as _}; +use reqwest::Client; +use serde_json::json; +use std::{cell::OnceCell, time::Duration}; +use tokio::time; +use tracing::*; + +use crate::config::{RebalancerAccount, RebalancerRule}; + +pub struct Rebalancer

{ + provider: P, + builder_client: reqwest::Client, + builder_url: String, + transfer_max_priority_fee_per_gas: u128, + accounts: HashMap>, + rules: Vec, + tracked_accounts: OnceCell, + timeout: Duration, + retry_delay: Duration, +} + +impl Rebalancer

{ + pub fn new( + provider: P, + builder_url: String, + transfer_max_priority_fee_per_gas: u128, + accounts: HashMap>, + rules: Vec, + timeout: Duration, + retry_delay: Duration, + ) -> Self { + let builder_client = reqwest::Client::builder().timeout(timeout).build().unwrap(); + Self { + provider, + builder_client, + builder_url, + transfer_max_priority_fee_per_gas, + accounts, + rules, + timeout, + retry_delay, + tracked_accounts: OnceCell::new(), + } + } + + fn tracked_accounts(&self) -> &AddressSet { + self.tracked_accounts.get_or_init(|| { + AddressSet::from_iter( + self.accounts + .values() + .map(|acc| acc.secret.address()) + .chain(self.rules.iter().map(|rule| rule.destination)), + ) + }) + } + + async fn fetch_accounts(&self, block_hash: B256) -> eyre::Result> { + let block_id = BlockId::hash(block_hash); + + let futs = FuturesUnordered::default(); + for target in self.tracked_accounts() { + futs.push(async move { + ( + *target, + time::timeout( + self.timeout, + self.provider.get_account_info(*target).block_id(block_id), + ) + .await, + ) + }); + } + + let mut infos = AddressMap::default(); + for (target, result) in futs.collect::>().await { + match result { + Ok(Ok(info)) => { + debug!(target: "rebalancer", %target, ?info, %block_hash, "Fetched account info for account"); + infos.insert(target, info); + } + // We don't want to rebalance if any of the account info fetches failed. + Ok(Err(error)) => { + return Err(eyre::eyre!( + "error fetching account info for {target}: {error}" + )); + } + Err(_) => return Err(eyre::eyre!("timed out fetching account info for {target}")), + } + } + + Ok(infos) + } + + async fn on_new_block(&self, header: Header) -> eyre::Result<()> { + info!(target: "rebalancer", number = header.number, hash = %header.hash, "Received new block"); + let accounts = self.fetch_accounts(header.hash).await?; + debug!(target: "rebalancer", number = header.number, hash = %header.hash, accounts = accounts.len(), "Updated account infos for tracked accounts"); + let mut transfers_by_source = HashMap::>::default(); + for rule in &self.rules { + let destination_balance = accounts + .get(&rule.destination) + .ok_or(eyre::eyre!("missing account for {}", rule.destination))? + .balance; + + if destination_balance > rule.destination_min_balance { + trace!(target: "rebalancer", number = header.number, hash = %header.hash, %rule.description, %rule.destination, %rule.destination_min_balance, %destination_balance, "Rebalancing destination balance above minimum"); + continue; + } + + let destination_target_delta = rule + .destination_target_balance + .checked_sub(destination_balance) + .expect("misconfiguration"); + + let transfer = Transfer { + destination: rule.destination, + amount: destination_target_delta, + description: rule.description.clone(), + }; + transfers_by_source + .entry(rule.source_id.clone()) + .or_default() + .push(transfer); + } + + for (source_id, transfers) in transfers_by_source { + let total_amount_out = transfers.iter().map(|t| t.amount).sum(); + + let source = self + .accounts + .get(&source_id) + .ok_or(eyre::eyre!("missing source {source_id}"))?; + let source_address = source.secret.address(); + let source_account = accounts + .get(&source_address) + .ok_or(eyre::eyre!("missing account {source_address}"))?; + let source_balance = source_account.balance; + + if source_balance + .checked_sub(total_amount_out) + .is_none_or(|final_balance| final_balance < source.min_balance) + { + let rules = transfers + .into_iter() + .map(|t| t.description) + .collect::>(); + warn!(target: "rebalancer", number = header.number, hash = %header.hash, %source_id, %source_address, %source_balance, %total_amount_out, ?rules, "Source account balance too low"); + continue; + } + + let client = self.builder_client.clone(); + let builder_url = self.builder_url.clone(); + let head = header.clone(); + let signer = source.secret.clone(); + let nonce = source_account.nonce; + let max_priority_fee_per_gas = self.transfer_max_priority_fee_per_gas; + tokio::spawn(async move { + if let Err(error) = send_system_transactions( + client, + &builder_url, + head.clone(), + signer, + nonce, + max_priority_fee_per_gas, + transfers, + ) + .await + { + error!(target: "rebalancer", number = head.number, hash = %head.hash, %source_id, %source_address, ?error, "Error sending system bundle"); + } + }); + } + + Ok(()) + } + + pub async fn run(self) -> eyre::Result<()> { + loop { + let mut subscription = self.provider.subscribe_blocks().await?.into_stream(); + while let Some(header) = subscription.next().await { + if let Err(error) = self.on_new_block(header.clone()).await { + error!(target: "rebalancer", number = header.number, hash = %header.hash, ?error, "Error handling block"); + } + } + warn!(target: "rebalancer", delay = ?self.retry_delay, "New block subscription has been terminated. Retrying..."); + tokio::time::sleep(self.retry_delay).await; + } + } +} + +async fn send_system_transactions( + client: Client, + builder_url: &str, + head: Header, + signer: PrivateKeySigner, + nonce: u64, + max_priority_fee_per_gas: u128, + transfers: Vec, +) -> eyre::Result<()> { + let signer_address = signer.address(); + let base_fee = head + .next_block_base_fee(BaseFeeParams::ethereum()) + .unwrap_or_default(); + let mut futs = FuturesUnordered::default(); + let mut next_nonce = nonce; + for transfer in transfers { + // Prepare transaction + let max_fee_per_gas = base_fee as u128 + max_priority_fee_per_gas; + let transaction = EthereumTypedTransaction::::Eip1559(TxEip1559 { + chain_id: 1, + nonce: next_nonce, + to: TxKind::Call(transfer.destination), + value: U256::from(transfer.amount), + gas_limit: 21_000, + max_fee_per_gas, + max_priority_fee_per_gas, + access_list: Default::default(), + input: Bytes::default(), + }); + let signature = signer.sign_hash_sync(&transaction.signature_hash())?; + let signed = EthereumTxEnvelope::::new_unhashed(transaction, signature); + + // Send the request to the builder + let client = client.clone(); + let tx_hash = *signed.hash(); + info!(target: "rebalancer", head_number = head.number, %head.hash, %signer_address, rule = %transfer.description, "Sending system transaction"); + futs.push( + async move { + let encoded = hex::encode_prefixed(signed.encoded_2718()); + let request = json!({ + "id": 1, + "jsonrpc": "2.0", + "method": "eth_sendRawTransaction", + "params": [encoded], + }); + let response = client.post(builder_url).json(&request).send().await?; + Ok::<_, eyre::Error>(response) + } + .map(move |result| (tx_hash, transfer, result)), + ); + + next_nonce += 1; + } + + while let Some((tx_hash, transfer, result)) = futs.next().await { + match result { + Ok(response) => { + let success = response.status().is_success(); + let text = response.text().await.ok(); + info!(target: "rebalancer", head_number = head.number, %head.hash, %signer_address, %tx_hash, rule = %transfer.description, success, response = ?text, "System transaction submitted"); + } + Err(error) => { + error!(target: "rebalancer", head_number = head.number, %head.hash, %signer_address, %tx_hash, rule = %transfer.description, ?error, "Error submitting system transaction") + } + } + } + + Ok(()) +} + +#[derive(Debug)] +struct Transfer { + destination: Address, + amount: U256, + description: String, +} diff --git a/crates/rbuilder/src/utils/tx_signer.rs b/crates/rbuilder/src/utils/tx_signer.rs index 1ef3210ef..891ca5544 100644 --- a/crates/rbuilder/src/utils/tx_signer.rs +++ b/crates/rbuilder/src/utils/tx_signer.rs @@ -53,6 +53,7 @@ mod test { use alloy_consensus::TxEip1559; use alloy_primitives::{address, fixed_bytes, TxKind as TransactionKind}; use reth_primitives_traits::SignerRecoverable; + #[test] fn test_sign_transaction() { let secret = @@ -64,8 +65,8 @@ mod test { let tx = Transaction::Eip1559(TxEip1559 { chain_id: 1, nonce: 2, - gas_limit: 21000, - max_fee_per_gas: 1000, + gas_limit: 21_000, + max_fee_per_gas: 0, max_priority_fee_per_gas: 20000, to: TransactionKind::Call(address), value: U256::from(3000u128), From f6254f32e2a918cc8d74ec9c17182b4ace223b05 Mon Sep 17 00:00:00 2001 From: File Large Date: Fri, 3 Oct 2025 06:39:25 +0200 Subject: [PATCH 2/2] clean up imports --- crates/rbuilder-rebalancer/src/bin/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rbuilder-rebalancer/src/bin/main.rs b/crates/rbuilder-rebalancer/src/bin/main.rs index 308ef74a5..c4c29656d 100644 --- a/crates/rbuilder-rebalancer/src/bin/main.rs +++ b/crates/rbuilder-rebalancer/src/bin/main.rs @@ -3,7 +3,7 @@ use alloy_signer_local::PrivateKeySigner; use clap::Parser; use rbuilder_rebalancer::{config::RebalancerConfig, rebalancer::Rebalancer}; use std::{path::PathBuf, str::FromStr, time::Duration}; -use tracing::{level_filters::LevelFilter, *}; +use tracing::*; #[tokio::main] async fn main() {