Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claudeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
17 changes: 17 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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/ ./
Expand All @@ -45,3 +61,4 @@ USER appuser

ENTRYPOINT ["doppler", "run", "--"]
CMD ["liquidation-bot-v3"]

17 changes: 12 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
});
}

Expand Down Expand Up @@ -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;
});
}

Expand Down
25 changes: 24 additions & 1 deletion src/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: Provider + WalletProvider>(
provider: T,
mut queue: Receiver<PreparedLiquidation>,
profit_receiver: Address,
state: Option<tokio::sync::watch::Sender<BotHealth>>,
) {
loop {
if let Some(liquidation) = queue.recv().await {
Expand Down Expand Up @@ -87,6 +88,23 @@ pub async fn execute_liquidation_queue<T: Provider + WalletProvider>(
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: {:?}",
Expand All @@ -96,6 +114,11 @@ pub async fn execute_liquidation_queue<T: Provider + WalletProvider>(
}
};

// 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() {
Expand Down