import { DLMMBot } from "../../src/bot"; import { MeteoraWrapper } from "../../src/meteora"; import logger from "../../src/utils/logger"; import { BN } from "@coral-xyz/anchor"; jest.mock("../../src/utils/config", () => ({ connection: { getActiveBin: jest.fn() }, wallet: { publicKey: { toBase58: () => "wallet1" } }, REBALANCE_THRESHOLD_PERCENT: 0.15, poolAddress: { toBase58: () => "pool1" }, DRY_RUN: false, SLIPPAGE_BPS: 100, MAX_RETRIES: 3, JLP_USD_ESTIMATE: 3.0, SOL_USD_ESTIMATE: 100.0, COMPOUND_THRESHOLD_USD: 1.0, })); jest.mock("../../src/utils/logger", () => ({ info: jest.fn(), error: jest.fn(), debug: jest.fn(), warn: jest.fn(), })); jest.mock("../../src/meteora"); describe("DLMMBot Rebalance Logic", () => { let bot: DLMMBot; let mockMeteora: jest.Mocked; beforeEach(() => { jest.clearAllMocks(); bot = new DLMMBot(); mockMeteora = (bot as any).meteora; mockMeteora.getBalances.mockResolvedValue({ balanceX: new BN(0), balanceY: new BN(0) }); mockMeteora.getTokenPrice.mockResolvedValue(1.0); mockMeteora.dlmmPool = { tokenX: { mint: { decimals: 9, address: { toBase58: () => "So11111111111111111111111111111111111111112" } } }, tokenY: { mint: { decimals: 6, address: { toBase58: () => "JLP" } } } } as any; }); it("should not rebalance if within range", async () => { mockMeteora.getActiveBin.mockResolvedValue({ binId: 100 } as any); mockMeteora.getPositions.mockResolvedValue([ { publicKey: { toBase58: () => "pos1" }, positionData: { lowerBinId: 90, upperBinId: 110 }, }, ] as any); await bot.rebalance(); expect(logger.info).toHaveBeenCalledWith( expect.stringContaining("Portfolio healthy") ); expect(mockMeteora.withdrawAll).not.toHaveBeenCalled(); }); it("should trigger rebalance if out of range", async () => { mockMeteora.getActiveBin.mockResolvedValue({ binId: 120 } as any); mockMeteora.getPositions.mockResolvedValue([ { publicKey: { toBase58: () => "pos1" }, positionData: { lowerBinId: 90, upperBinId: 110 }, }, ] as any); mockMeteora.getBalances.mockResolvedValue({ balanceX: new BN(100), balanceY: new BN(100) }); mockMeteora.getTokenPrice.mockResolvedValue(1.0); // dlmmPool already mocked in beforeEach await bot.rebalance(); expect(mockMeteora.withdrawAll).toHaveBeenCalled(); expect(mockMeteora.getBalances).toHaveBeenCalled(); expect(mockMeteora.deposit).toHaveBeenCalledWith( new BN(100), new BN(100), 120 ); }); });