import { handleFetch } from '@metamask/controller-utils'; import { CHAIN_IDS, ETHERSCAN_SUPPORTED_NETWORKS, } from '../../../../shared/constants/network'; import type { EtherscanTransactionMeta, EtherscanTransactionRequest, EtherscanTransactionResponse, } from './etherscan'; import * as Etherscan from './etherscan'; jest.mock('@metamask/controller-utils', () => ({ ...jest.requireActual('@metamask/controller-utils'), handleFetch: jest.fn(), })); const ADDERSS_MOCK = '0x2A2D72308838A6A46a0B5FDA3055FE915b5D99eD'; const REQUEST_MOCK: EtherscanTransactionRequest = { address: ADDERSS_MOCK, chainId: CHAIN_IDS.GOERLI, limit: 3, fromBlock: 2, apiKey: 'testApiKey', }; const RESPONSE_MOCK: EtherscanTransactionResponse = { result: [ { from: ADDERSS_MOCK, nonce: '0x1' } as EtherscanTransactionMeta, { from: ADDERSS_MOCK, nonce: '0x2' } as EtherscanTransactionMeta, ], }; describe('Etherscan', () => { const handleFetchMock = handleFetch as jest.MockedFunction< typeof handleFetch >; beforeEach(() => { jest.resetAllMocks(); }); describe.each([ ['fetchEtherscanTransactions', 'txlist'], ['fetchEtherscanTokenTransactions', 'tokentx'], ])('%s', (method, action) => { it('returns fetched response', async () => { handleFetchMock.mockResolvedValueOnce(RESPONSE_MOCK); const result = await (Etherscan as any)[method](REQUEST_MOCK); expect(result).toStrictEqual(RESPONSE_MOCK); }); it('fetches from Etherscan URL', async () => { handleFetchMock.mockResolvedValueOnce(RESPONSE_MOCK); await (Etherscan as any)[method](REQUEST_MOCK); expect(handleFetchMock).toHaveBeenCalledTimes(1); expect(handleFetchMock).toHaveBeenCalledWith( `https://${ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.GOERLI].subdomain}.${ ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.GOERLI].domain }/api?` + `module=account` + `&address=${REQUEST_MOCK.address}` + `&startBlock=${REQUEST_MOCK.fromBlock}` + `&apikey=${REQUEST_MOCK.apiKey}` + `&offset=${REQUEST_MOCK.limit}` + `&order=desc` + `&action=${action}` + `&tag=latest` + `&page=1`, ); }); it('supports alternate networks', async () => { handleFetchMock.mockResolvedValueOnce(RESPONSE_MOCK); await (Etherscan as any)[method]({ ...REQUEST_MOCK, chainId: CHAIN_IDS.MAINNET, }); expect(handleFetchMock).toHaveBeenCalledTimes(1); expect(handleFetchMock).toHaveBeenCalledWith( `https://${ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.MAINNET].subdomain}.${ ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.MAINNET].domain }/api?` + `module=account` + `&address=${REQUEST_MOCK.address}` + `&startBlock=${REQUEST_MOCK.fromBlock}` + `&apikey=${REQUEST_MOCK.apiKey}` + `&offset=${REQUEST_MOCK.limit}` + `&order=desc` + `&action=${action}` + `&tag=latest` + `&page=1`, ); }); it('throws if message is not ok', async () => { handleFetchMock.mockResolvedValueOnce({ status: '0', message: 'NOTOK', result: 'test error', }); await expect((Etherscan as any)[method](REQUEST_MOCK)).rejects.toThrow( 'Etherscan request failed - test error', ); }); it('throws if chain is not supported', async () => { const unsupportedChainId = '0x11111111111111111111'; await expect( (Etherscan as any)[method]({ ...REQUEST_MOCK, chainId: unsupportedChainId, }), ).rejects.toThrow( `Etherscan does not support chain with ID: ${unsupportedChainId}`, ); }); it('does not include empty values in fetched URL', async () => { handleFetchMock.mockResolvedValueOnce(RESPONSE_MOCK); await (Etherscan as any)[method]({ ...REQUEST_MOCK, fromBlock: undefined, apiKey: undefined, }); expect(handleFetchMock).toHaveBeenCalledTimes(1); expect(handleFetchMock).toHaveBeenCalledWith( `https://${ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.GOERLI].subdomain}.${ ETHERSCAN_SUPPORTED_NETWORKS[CHAIN_IDS.GOERLI].domain }/api?` + `module=account` + `&address=${REQUEST_MOCK.address}` + `&offset=${REQUEST_MOCK.limit}` + `&order=desc` + `&action=${action}` + `&tag=latest` + `&page=1`, ); }); }); });