import { PublicKey } from "@solana/web3.js"; import { BN } from "@coral-xyz/anchor"; // Objects prefixed with 'mock' are hoisted and allowed in jest.mock factory const mockInternalConfig = { connection: {}, wallet: { publicKey: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ") }, poolAddress: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ"), DRY_RUN: false, SLIPPAGE_BPS: 100, MAX_RETRIES: 3, MIN_SOL_FOR_GAS: 0.1, }; jest.mock("../../src/utils/config", () => mockInternalConfig); jest.mock("@meteora-ag/dlmm"); jest.mock("@solana/web3.js", () => ({ ...jest.requireActual("@solana/web3.js"), sendAndConfirmTransaction: jest.fn(), })); jest.mock("../../src/utils/logger", () => ({ info: jest.fn(), error: jest.fn(), debug: jest.fn(), warn: jest.fn(), })); import { MeteoraWrapper } from "../../src/meteora"; import DLMM from "@meteora-ag/dlmm"; import { sendAndConfirmTransaction } from "@solana/web3.js"; import logger from "../../src/utils/logger"; describe("MeteoraWrapper", () => { let wrapper: MeteoraWrapper; let mockDlmm: any; beforeEach(async () => { jest.clearAllMocks(); wrapper = new MeteoraWrapper(); mockDlmm = { getActiveBin: jest.fn(), getPositionsByUserAndLbPair: jest.fn(), removeLiquidity: jest.fn(), initializePositionAndAddLiquidityByStrategy: jest.fn(), claimAllRewardsByPosition: jest.fn(), tokenX: { mint: { address: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ") } }, tokenY: { mint: { address: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ") } }, }; (DLMM.create as jest.Mock).mockResolvedValue(mockDlmm); await wrapper.init(); }); it("should get active bin", async () => { mockDlmm.getActiveBin.mockResolvedValue({ binId: 100 }); const bin = await wrapper.getActiveBin(); expect(bin.binId).toBe(100); }); it("should withdraw all liquidity from positions", async () => { const mockPosition = { publicKey: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ"), positionData: { lowerBinId: 90, upperBinId: 110 }, }; mockDlmm.getPositionsByUserAndLbPair.mockResolvedValue({ userPositions: [mockPosition], }); mockDlmm.removeLiquidity.mockResolvedValue([{}]); (sendAndConfirmTransaction as jest.Mock).mockResolvedValue("txHash"); await wrapper.withdrawAll(); expect(mockDlmm.removeLiquidity).toHaveBeenCalled(); expect(sendAndConfirmTransaction).toHaveBeenCalled(); }); it("should deposit liquidity", async () => { mockDlmm.initializePositionAndAddLiquidityByStrategy.mockResolvedValue({}); (sendAndConfirmTransaction as jest.Mock).mockResolvedValue("txHash"); await wrapper.deposit( new BN(100), new BN(100), 100 ); expect( mockDlmm.initializePositionAndAddLiquidityByStrategy ).toHaveBeenCalled(); expect(sendAndConfirmTransaction).toHaveBeenCalled(); }); it("should skip transactions in DRY_RUN mode", async () => { mockInternalConfig.DRY_RUN = true; mockDlmm.initializePositionAndAddLiquidityByStrategy.mockResolvedValue({}); await wrapper.deposit( new BN(100), new BN(100), 100 ); expect(sendAndConfirmTransaction).not.toHaveBeenCalled(); expect(logger.info).toHaveBeenCalledWith( expect.stringContaining("[DRY RUN]") ); mockInternalConfig.DRY_RUN = false; // Reset }); it("should get token balances", async () => { mockDlmm.tokenX = { mint: { address: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ") } }; mockDlmm.tokenY = { mint: { address: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ") } }; const balances = await wrapper.getBalances(); expect(balances).toBeDefined(); }); it("should log deposit details", async () => { mockDlmm.initializePositionAndAddLiquidityByStrategy.mockResolvedValue({}); mockDlmm.tokenX = { mint: { address: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ") } }; mockDlmm.tokenY = { mint: { address: new PublicKey("LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ") } }; (sendAndConfirmTransaction as jest.Mock).mockResolvedValue("txHash"); await wrapper.deposit(new BN(100), new BN(100), 100); expect(logger.info).toHaveBeenCalledWith( expect.stringContaining("Depositing liquidity") ); }); });