Skip to main content
Glama

MCP Ethers Wallet

erc20.test.ts.md7.28 kB
/** * @file ERC20 Helper Tests */ import { ethers } from 'ethers'; import { EthersService } from '../ethersService.js'; import * as erc20 from './erc20.js'; import { InsufficientBalanceError, TokenNotFoundError } from './errors.js'; import { balanceCache, contractCache } from '../../utils/cache.js'; // Mock EthersService jest.mock('../ethersService'); // Mock cache jest.mock('../../utils/cache', () => ({ balanceCache: { get: jest.fn(), set: jest.fn(), delete: jest.fn(), }, contractCache: { get: jest.fn(), set: jest.fn(), }, })); // Mock metrics and logger jest.mock('../../utils/metrics', () => ({ metrics: { incrementCounter: jest.fn(), }, timeAsync: jest.fn((name, fn) => fn()), })); jest.mock('../../utils/logger', () => ({ logger: { debug: jest.fn(), }, })); // Mock rate limiter jest.mock('../../utils/rateLimiter', () => ({ rateLimiter: { consume: jest.fn().mockReturnValue(true), }, })); describe('ERC20 Helpers', () => { let mockEthersService: jest.Mocked<EthersService>; let mockProvider: jest.Mocked<ethers.Provider>; let mockSigner: jest.Mocked<ethers.Signer>; let mockContract: jest.Mocked<ethers.Contract>; const TEST_TOKEN_ADDRESS = '0x1234567890123456789012345678901234567890'; const TEST_OWNER_ADDRESS = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd'; const TEST_RECIPIENT_ADDRESS = '0x9876543210987654321098765432109876543210'; beforeEach(() => { jest.clearAllMocks(); // Create mock provider mockProvider = { getCode: jest.fn().mockResolvedValue('0x123'), } as unknown as jest.Mocked<ethers.Provider>; // Create mock signer mockSigner = { getAddress: jest.fn().mockResolvedValue(TEST_OWNER_ADDRESS), } as unknown as jest.Mocked<ethers.Signer>; // Create mock contract mockContract = { name: jest.fn().mockResolvedValue('Test Token'), symbol: jest.fn().mockResolvedValue('TEST'), decimals: jest.fn().mockResolvedValue(18), totalSupply: jest.fn().mockResolvedValue(ethers.parseEther('1000000')), balanceOf: jest.fn().mockResolvedValue(ethers.parseEther('100')), allowance: jest.fn().mockResolvedValue(ethers.parseEther('50')), transfer: jest.fn().mockResolvedValue({ hash: '0xabcd', wait: jest.fn() }), approve: jest.fn().mockResolvedValue({ hash: '0xabcd', wait: jest.fn() }), transferFrom: jest.fn().mockResolvedValue({ hash: '0xabcd', wait: jest.fn() }), } as unknown as jest.Mocked<ethers.Contract>; // Create mock EthersService mockEthersService = { getProvider: jest.fn().mockReturnValue(mockProvider), getSigner: jest.fn().mockReturnValue(mockSigner), } as unknown as jest.Mocked<EthersService>; // Mock ethers.Contract constructor jest.spyOn(ethers, 'Contract').mockImplementation(() => mockContract); }); describe('getTokenInfo', () => { it('should return token info from cache if available', async () => { const cachedInfo = { name: 'Cached Token', symbol: 'CACHE', decimals: 18, totalSupply: '1000000000000000000000000' }; (contractCache.get as jest.Mock).mockReturnValue(cachedInfo); const result = await erc20.getTokenInfo( mockEthersService, TEST_TOKEN_ADDRESS ); expect(result).toEqual(cachedInfo); expect(contractCache.get).toHaveBeenCalled(); expect(mockEthersService.getProvider).not.toHaveBeenCalled(); }); it('should fetch token info from blockchain if not in cache', async () => { (contractCache.get as jest.Mock).mockReturnValue(null); const result = await erc20.getTokenInfo( mockEthersService, TEST_TOKEN_ADDRESS ); expect(result).toEqual({ name: 'Test Token', symbol: 'TEST', decimals: 18, totalSupply: '1000000000000000000000000' }); expect(mockEthersService.getProvider).toHaveBeenCalled(); expect(contractCache.set).toHaveBeenCalled(); expect(mockContract.name).toHaveBeenCalled(); expect(mockContract.symbol).toHaveBeenCalled(); expect(mockContract.decimals).toHaveBeenCalled(); expect(mockContract.totalSupply).toHaveBeenCalled(); }); it('should throw TokenNotFoundError if contract does not exist', async () => { (contractCache.get as jest.Mock).mockReturnValue(null); mockProvider.getCode.mockResolvedValue('0x'); await expect( erc20.getTokenInfo(mockEthersService, TEST_TOKEN_ADDRESS) ).rejects.toThrow(TokenNotFoundError); }); }); describe('getBalance', () => { it('should return balance from cache if available', async () => { const cachedBalance = '100.0'; (balanceCache.get as jest.Mock).mockReturnValue(cachedBalance); const result = await erc20.getBalance( mockEthersService, TEST_TOKEN_ADDRESS, TEST_OWNER_ADDRESS ); expect(result).toEqual(cachedBalance); expect(balanceCache.get).toHaveBeenCalled(); expect(mockEthersService.getProvider).not.toHaveBeenCalled(); }); it('should fetch balance from blockchain if not in cache', async () => { (balanceCache.get as jest.Mock).mockReturnValue(null); (contractCache.get as jest.Mock).mockReturnValue({ name: 'Test Token', symbol: 'TEST', decimals: 18, totalSupply: '1000000000000000000000000' }); const result = await erc20.getBalance( mockEthersService, TEST_TOKEN_ADDRESS, TEST_OWNER_ADDRESS ); expect(result).toEqual('100.0'); expect(mockEthersService.getProvider).toHaveBeenCalled(); expect(balanceCache.set).toHaveBeenCalled(); expect(mockContract.balanceOf).toHaveBeenCalledWith(TEST_OWNER_ADDRESS); }); }); describe('transfer', () => { it('should transfer tokens successfully', async () => { (contractCache.get as jest.Mock).mockReturnValue({ name: 'Test Token', symbol: 'TEST', decimals: 18, totalSupply: '1000000000000000000000000' }); const result = await erc20.transfer( mockEthersService, TEST_TOKEN_ADDRESS, TEST_RECIPIENT_ADDRESS, '10' ); expect(result).toEqual({ hash: '0xabcd', wait: expect.any(Function) }); expect(mockContract.transfer).toHaveBeenCalledWith( TEST_RECIPIENT_ADDRESS, ethers.parseEther('10'), {} ); expect(balanceCache.delete).toHaveBeenCalledTimes(2); }); it('should throw InsufficientBalanceError if balance is too low', async () => { (contractCache.get as jest.Mock).mockReturnValue({ name: 'Test Token', symbol: 'TEST', decimals: 18, totalSupply: '1000000000000000000000000' }); mockContract.balanceOf.mockResolvedValue(ethers.parseEther('5')); await expect( erc20.transfer( mockEthersService, TEST_TOKEN_ADDRESS, TEST_RECIPIENT_ADDRESS, '10' ) ).rejects.toThrow(InsufficientBalanceError); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/crazyrabbitLTC/mcp-ethers-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server