Hi!
I am trying to deploy an onchain bot which is a typescript app, but it doesnt use a server. I tried referring to your existing docs, but it does not have any helpful info. The script looks like this:
/* eslint-disable no-undef */
import { LiquidationStateTracker } from "../bot-classes/viem-reader";
import { PlatformHandler } from "../bot-classes/viem-manager-sdk";
import { wagmiContractConfig } from "../constants";
import { avalanche } from "viem/chains";
import { Address, Hex } from "viem";
import * as envEnc from "@chainlink/env-enc";
envEnc.config();
// Configuration
const RPC_URL = process.env.AVAX_RPC_URL!;
const privateKey = process.env.PRIVATE_KEY!;
const INTERVAL_MS = 2500;
const MAX_RETRIES = 2;
const RETRY_DELAY = 800;
// Define assets to monitor
const ASSETS = {
bitcon: "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" as Hex,
ethereum:
"0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace" as Hex,
avalanche:
"0x93da3352f9f1d105fdfe4971cfa80e9dd777bfc5d0f683ebb6e1294b92137bb7" as Hex,
solana: "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d" as Hex,
ripple: "0xec5d399846a9209f3fe5881d70aae9268c94339ff9817e8d18ff19fa05eea1c8" as Hex,
binance: "0x2f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f" as Hex,
euro: "0xa995d00bb36a63cef7fd2c287dc105fc8f3d93779f062f09551b0af3e81ec30b" as Hex,
pounds: "0x84c2dde9633d93d1bcad84e7dc41c9d56578b7ec52fabedc1f335d673df0a7c1" as Hex,
yen: "0xef2c98c804ba503c6a707e38be4dfbb16683775f195b091252bf24693042fd52" as Hex,
gold: "0x765d2ba906dbc32ca17cc11f5310a89e9ee1f6420508c63861f2f8ba4ee34bb2" as Hex,
silver: "0xf2fb02c32b055c805e7238d628e5e9dadef274376114eb1f012337cabe93871e" as Hex,
us_oil: "0x925ca92ff005ae943c158e3563f59698ce7e75c5a8c8dd43303a0a154887b3e6" as Hex
};
// Track timing for each asset
const lastCycleTimes: Record<string, number> = Object.keys(ASSETS).reduce(
(acc, asset) => ({
...acc,
[asset]: Date.now(),
}),
{}
);
// Initialize handlers (moved inside function to prevent memory leaks)
let liquidationStateTracker: LiquidationStateTracker | null = null;
let platformHandler: PlatformHandler | null = null;
let isMonitoring = false;
let intervalId: NodeJS.Timeout | null = null;
// Helper function to initialize handlers
function initializeHandlers() {
if (!liquidationStateTracker || !platformHandler) {
liquidationStateTracker = new LiquidationStateTracker(
RPC_URL,
wagmiContractConfig.marketRegistry.address as Address,
avalanche
);
platformHandler = new PlatformHandler(
wagmiContractConfig.marketRegistry.address as Address,
avalanche,
RPC_URL,
privateKey
);
}
}
// Retry mechanism with exponential backoff
async function retryOperation<T>(
operation: () => Promise<T>,
retries: number = MAX_RETRIES
): Promise<T> {
for (let i = 0; i < retries; i++) {
try {
return await operation();
} catch (error) {
if (i === retries - 1) throw error;
await new Promise((resolve) =>
setTimeout(resolve, RETRY_DELAY * Math.pow(2, i))
);
}
}
throw new Error("Operation failed after retries");
}
async function handlePositions(
positions: string[],
handler: PlatformHandler,
pricefeedId: Hex,
assetName: string
): Promise<void> {
if (!positions.length) return;
try {
const results = await retryOperation(() =>
handler.liquidatePositionsByIds(positions, pricefeedId)
);
const summary = handler.getBatchLiquidationSummary(results);
console.log(
`[${assetName}] Successfully liquidated ${summary.totalSuccessful} positions`
);
} catch (error) {
console.error(`[${assetName}] position error:`, error);
}
}
async function checkAndLiquidateAsset(
tracker: LiquidationStateTracker,
handler: PlatformHandler,
pricefeedId: Hex,
assetName: string
): Promise<void> {
try {
const roguePositionsResult = await tracker.getLiquidatableIds(pricefeedId);
if (roguePositionsResult.length > 0) {
await handlePositions(roguePositionsResult, handler, pricefeedId, assetName);
}
} catch (error) {
console.error(`[${assetName}] Cycle error:`, error);
}
}
async function checkAndLiquidateAll(
tracker: LiquidationStateTracker,
handler: PlatformHandler
): Promise<void> {
try {
await Promise.allSettled(
Object.entries(ASSETS).map(async ([assetName, pricefeedId]) => {
const currentTime = Date.now();
const timeSinceLastCycle = currentTime - lastCycleTimes[assetName];
const timeString = new Date().toLocaleTimeString("en-GB");
console.log(
`[${timeString}][${assetName}] Time since last cycle: ${timeSinceLastCycle}ms`
);
lastCycleTimes[assetName] = currentTime;
return checkAndLiquidateAsset(tracker, handler, pricefeedId, assetName);
})
);
} catch (error) {
console.error("Error in main liquidation cycle:", error);
}
}
function cleanup(): void {
isMonitoring = false;
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
liquidationStateTracker = null;
platformHandler = null;
}
async function startMonitoring(): Promise<void> {
console.log({ isMonitoring });
if (isMonitoring) return;
try {
isMonitoring = true;
initializeHandlers();
if (!liquidationStateTracker || !platformHandler) {
throw new Error("Failed to initialize handlers");
}
console.log("Starting multi-asset monitoring...");
console.log("Monitoring assets:", Object.keys(ASSETS).join(", "));
const runCycle = () => {
if (!isMonitoring || !liquidationStateTracker || !platformHandler) return;
checkAndLiquidateAll(liquidationStateTracker, platformHandler).catch(
console.error
);
};
runCycle();
intervalId = setInterval(runCycle, INTERVAL_MS);
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
process.on("uncaughtException", (error) => {
console.error("Uncaught exception:", error);
cleanup();
});
} catch (error) {
console.error("Monitoring error:", error);
cleanup();
}
}
// Start monitoring
startMonitoring();