diff --git a/.claudeignore b/.claudeignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.claudeignore @@ -0,0 +1 @@ +.env diff --git a/Dockerfile b/Dockerfile index 18604ba..3f4640b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,16 @@ RUN cargo chef cook --release --recipe-path recipe.json COPY . . RUN cargo build --release +# ---- Foundry Stage (download prebuilt binaries) ---- +FROM debian:bookworm-slim AS foundry +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git \ + && rm -rf /var/lib/apt/lists/* +# Pin a release for reproducibility — bump as needed +ARG FOUNDRY_VERSION=stable +RUN curl -L https://foundry.paradigm.xyz | bash \ + && /root/.foundry/bin/foundryup --install ${FOUNDRY_VERSION} + # ---- Runtime Stage ---- FROM debian:bookworm-slim @@ -33,6 +43,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get autoremove -y \ && rm -rf /var/lib/apt/lists/* +# Copy Foundry binaries from the foundry stage +COPY --from=foundry /root/.foundry/bin/forge /usr/local/bin/forge +COPY --from=foundry /root/.foundry/bin/cast /usr/local/bin/cast +COPY --from=foundry /root/.foundry/bin/anvil /usr/local/bin/anvil +COPY --from=foundry /root/.foundry/bin/chisel /usr/local/bin/chisel + # Copy the compiled binary from the builder COPY --from=builder /app/target/release/liquidation-bot-v3 /usr/local/bin/liquidation-bot-v3 COPY --from=builder /app/configs/ ./ @@ -45,3 +61,4 @@ USER appuser ENTRYPOINT ["doppler", "run", "--"] CMD ["liquidation-bot-v3"] + diff --git a/src/main.rs b/src/main.rs index f1f6a53..f238ccf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -195,13 +195,20 @@ async fn main() { ), }; + let (tx, rx) = tokio::sync::watch::channel(BotHealth::Syncing); + let profit_receiver = config.profit_receiver; + let liquidation_health_tx = tx.clone(); tokio::spawn(async move { - execute_liquidation_queue(liquidation_provider, liquidation_receiver, profit_receiver).await + execute_liquidation_queue( + liquidation_provider, + liquidation_receiver, + profit_receiver, + Some(liquidation_health_tx), + ) + .await }); - let (tx, rx) = tokio::sync::watch::channel(BotHealth::Syncing); - if config.enable_observability_api { // Start the observability api. let state = BotState { @@ -820,7 +827,7 @@ mod test { .connect_http(network.endpoint_url()); tokio::spawn(async move { - execute_liquidation_queue(provider, liquidation_receiver, recipient).await; + execute_liquidation_queue(provider, liquidation_receiver, recipient, None).await; }); } @@ -1002,7 +1009,7 @@ mod test { .connect_http(network.endpoint_url()); tokio::spawn(async move { - execute_liquidation_queue(provider, liquidation_receiver, recipient).await; + execute_liquidation_queue(provider, liquidation_receiver, recipient, None).await; }); } diff --git a/src/transactions.rs b/src/transactions.rs index 9e2844b..df20c42 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -6,13 +6,14 @@ use alloy::{ use tokio::sync::mpsc::Receiver; use tracing::{error, info, warn}; -use crate::liquidation::PreparedLiquidation; +use crate::{api::BotHealth, liquidation::PreparedLiquidation}; /// Watches the liquidation channel and executes liquidations. pub async fn execute_liquidation_queue( provider: T, mut queue: Receiver, profit_receiver: Address, + state: Option>, ) { loop { if let Some(liquidation) = queue.recv().await { @@ -87,6 +88,23 @@ pub async fn execute_liquidation_queue( let tx = match provider.send_transaction(tx).await { Ok(tx) => tx.get_receipt().await, Err(err) => { + // We check to see if this error is because of having insuffecient funds to + // execute the transaction. If so then this bot is now in an unhealthy state. + match err { + alloy::transports::RpcError::ErrorResp(ref error_payload) => { + // -32003 is "Transaction Rejected" + // We then check to make sure it is specifically insuffecient funds. + if error_payload.code == -32003 + && error_payload.message.contains("insufficient") + { + if let Some(ref state) = state { + state.send(BotHealth::Error("Insuffecient funds".to_string())); + } + } + } + _ => {} + }; + error!( account =? liquidation.account(), "Issue sending transaction, err: {:?}", @@ -96,6 +114,11 @@ pub async fn execute_liquidation_queue( } }; + // If we are processing transactions then we should be healthy. + if let Some(ref state) = state { + let _ = state.send(BotHealth::Healthy); + } + match tx { Ok(receipt) => { if receipt.status() {