import { createSuccessResponse, createErrorResponse } from "../../src/utils/responses";
import { validateSwapParams, executeSwap, estimateSwapGas } from "../../src/utils/swap";
import { validateWalletConfig, getChainConfig } from "../../src/utils/config";
// Mock all the utilities
jest.mock("../../src/utils/swap");
jest.mock("../../src/utils/config");
jest.mock("../../src/utils/responses");
const mockValidateSwapParams = validateSwapParams as jest.MockedFunction<typeof validateSwapParams>;
const mockExecuteSwap = executeSwap as jest.MockedFunction<typeof executeSwap>;
const mockEstimateSwapGas = estimateSwapGas as jest.MockedFunction<typeof estimateSwapGas>;
const mockValidateWalletConfig = validateWalletConfig as jest.MockedFunction<
typeof validateWalletConfig
>;
const mockGetChainConfig = getChainConfig as jest.MockedFunction<typeof getChainConfig>;
const mockCreateSuccessResponse = createSuccessResponse as jest.MockedFunction<
typeof createSuccessResponse
>;
const mockCreateErrorResponse = createErrorResponse as jest.MockedFunction<
typeof createErrorResponse
>;
// Create handler functions directly since mocking imports is problematic
async function executeSwapHandler(args: any) {
try {
const {
amount,
tokenInAddress,
tokenOutAddress,
slippage = 1,
receiverAddress,
chainName,
} = args;
// Validate wallet configuration
const walletValidation = validateWalletConfig();
if (!walletValidation.isValid) {
return createErrorResponse(
`Wallet configuration invalid: ${walletValidation.errors.join(", ")}`,
"execute-swap",
chainName
);
}
// Validate swap parameters
const validation = validateSwapParams({
amount,
tokenInAddress,
tokenOutAddress,
slippage,
receiverAddress,
chainName,
});
if (!validation.isValid) {
return createErrorResponse(
`Invalid swap parameters: ${validation.errors.join(", ")}`,
"execute-swap",
chainName
);
}
// Get chain configuration
const chainConfig = getChainConfig(chainName);
// Execute swap
const result = await executeSwap(
{ amount, tokenInAddress, tokenOutAddress, slippage, receiverAddress, chainName },
chainConfig,
{} as any // Mock router
);
if (!result.success) {
return createErrorResponse(result.error || "Swap failed", "execute-swap", chainName);
}
return createSuccessResponse(result, `Swap executed successfully on ${chainName}`);
} catch (error) {
return createErrorResponse(error, "execute-swap");
}
}
async function estimateSwapHandler(args: any) {
try {
const { amount, tokenInAddress, tokenOutAddress, slippage = 1, chainName } = args;
// Validate wallet configuration
const walletValidation = validateWalletConfig();
if (!walletValidation.isValid) {
return createErrorResponse(
`Wallet configuration invalid: ${walletValidation.errors.join(", ")}`,
"estimate-swap",
chainName
);
}
// Get chain configuration
const chainConfig = getChainConfig(chainName);
// Estimate gas
const result = await estimateSwapGas(
{ amount, tokenInAddress, tokenOutAddress, slippage, chainName },
chainConfig,
{} as any // Mock router
);
return createSuccessResponse(result, `Gas estimation for ${chainName} swap`);
} catch (error) {
return createErrorResponse(error, "estimate-swap");
}
}
describe("Swap Tools", () => {
beforeEach(() => {
jest.clearAllMocks();
// Setup default mocks
mockValidateWalletConfig.mockReturnValue({ isValid: true, errors: [] });
mockGetChainConfig.mockReturnValue({
rpcUrl: "https://test-rpc.com",
privateKey: "0x1234567890123456789012345678901234567890123456789012345678901234",
publicKey: "0x1234567890123456789012345678901234567890123456789012345678901234",
});
mockCreateSuccessResponse.mockImplementation((data, message) => ({
content: [{ type: "text", text: `${message}:\n\n${JSON.stringify(data, null, 2)}` }],
}));
mockCreateErrorResponse.mockImplementation((error, context) => ({
isError: true,
content: [
{
type: "text",
text: `Error in ${context}: ${error instanceof Error ? error.message : error}`,
},
],
}));
});
describe("executeSwapHandler", () => {
const validSwapArgs = {
amount: "1000000000000000000",
tokenInAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
tokenOutAddress: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
slippage: 1,
receiverAddress: "0x1234567890123456789012345678901234567890",
chainName: "starknet",
};
it("should execute swap successfully", async () => {
mockValidateSwapParams.mockReturnValue({ isValid: true, errors: [] });
mockExecuteSwap.mockResolvedValue({
success: true,
transactionHash: "0xabc123",
explorerUrl: "https://starkscan.co/tx/0xabc123",
gasUsed: "150000",
});
const result = await executeSwapHandler(validSwapArgs);
expect(mockValidateSwapParams).toHaveBeenCalledWith({
amount: validSwapArgs.amount,
tokenInAddress: validSwapArgs.tokenInAddress,
tokenOutAddress: validSwapArgs.tokenOutAddress,
slippage: validSwapArgs.slippage,
receiverAddress: validSwapArgs.receiverAddress,
chainName: validSwapArgs.chainName,
});
expect(mockExecuteSwap).toHaveBeenCalled();
expect(result.content[0].text).toContain("Swap executed successfully on starknet");
});
it("should handle wallet configuration validation failure", async () => {
mockValidateWalletConfig.mockReturnValue({
isValid: false,
errors: ["Missing private key for starknet"],
});
const result = await executeSwapHandler(validSwapArgs);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("Missing private key for starknet");
});
it("should handle swap parameter validation failure", async () => {
mockValidateSwapParams.mockReturnValue({
isValid: false,
errors: ["Amount must be greater than 0"],
});
const result = await executeSwapHandler(validSwapArgs);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("Amount must be greater than 0");
});
it("should handle swap execution failure", async () => {
mockValidateSwapParams.mockReturnValue({ isValid: true, errors: [] });
mockExecuteSwap.mockResolvedValue({
success: false,
error: "Insufficient balance",
});
const result = await executeSwapHandler(validSwapArgs);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("Insufficient balance");
});
it("should handle unexpected errors gracefully", async () => {
mockValidateSwapParams.mockReturnValue({ isValid: true, errors: [] });
mockExecuteSwap.mockRejectedValue(new Error("Network error"));
const result = await executeSwapHandler(validSwapArgs);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("Network error");
});
});
describe("estimateSwapHandler", () => {
const validEstimateArgs = {
amount: "1000000000000000000",
tokenInAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
tokenOutAddress: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
slippage: 2,
chainName: "starknet",
};
it("should estimate swap gas successfully", async () => {
mockEstimateSwapGas.mockResolvedValue({
gasEstimate: "150000",
gasPrice: "1000000000",
estimatedCost: "150000000000000",
});
const result = await estimateSwapHandler(validEstimateArgs);
expect(mockEstimateSwapGas).toHaveBeenCalledWith(
{
amount: validEstimateArgs.amount,
tokenInAddress: validEstimateArgs.tokenInAddress,
tokenOutAddress: validEstimateArgs.tokenOutAddress,
slippage: validEstimateArgs.slippage,
chainName: validEstimateArgs.chainName,
},
expect.any(Object),
expect.any(Object)
);
expect(result.content[0].text).toContain("Gas estimation for starknet swap");
expect(result.content[0].text).toContain("150000");
});
it("should handle wallet configuration validation failure", async () => {
mockValidateWalletConfig.mockReturnValue({
isValid: false,
errors: ["Missing RPC URL for starknet"],
});
const result = await estimateSwapHandler(validEstimateArgs);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("Missing RPC URL for starknet");
});
it("should handle gas estimation failure", async () => {
mockEstimateSwapGas.mockRejectedValue(new Error("RPC endpoint unavailable"));
const result = await estimateSwapHandler(validEstimateArgs);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain("RPC endpoint unavailable");
});
it("should use default slippage when not provided", async () => {
const argsWithoutSlippage = {
amount: validEstimateArgs.amount,
tokenInAddress: validEstimateArgs.tokenInAddress,
tokenOutAddress: validEstimateArgs.tokenOutAddress,
chainName: validEstimateArgs.chainName,
};
mockEstimateSwapGas.mockResolvedValue({
gasEstimate: "150000",
gasPrice: "1000000000",
estimatedCost: "150000000000000",
});
await estimateSwapHandler(argsWithoutSlippage);
expect(mockEstimateSwapGas).toHaveBeenCalledWith(
expect.objectContaining({ slippage: 1 }), // Default slippage
expect.any(Object),
expect.any(Object)
);
});
});
});