/** * @jest-environment jsdom */ import portal from '../__mocks/portal/portal' import Provider from '.' import { mockRpcUrl, mockSignedHash } from '../__mocks/constants' import { ProviderRpcError, RpcErrorCodes } from './errors' import { RequestMethod } from '../' import mpcMock from '../__mocks/portal/mpc' import { sdkLogger } from '../logger' import { X_PORTAL_TRACE_ID_HEADER } from '../shared/trace' jest.mock('../shared/trace', () => ({ ...jest.requireActual('../shared/trace'), generateTraceId: jest.fn(() => 'mock-trace-id-12345'), X_PORTAL_TRACE_ID_HEADER: 'X-Portal-Trace-Id', })) describe('Provider', () => { beforeEach(() => { portal.autoApprove = true portal.mpc = mpcMock jest.clearAllMocks() global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue({ result: 'test', error: null }), }) }) describe('request', () => { let provider = new Provider({ portal }) describe('Signer methods with auto-approval', () => { beforeAll(() => { portal.autoApprove = true provider.emit = jest.fn() }) describe('eth_chainId', () => { it('should throw an error if no chainId is provided alongside eth_chainId', async () => { expect( provider.request({ method: RequestMethod.eth_chainId, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_chainId', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_chainId, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_chainId request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_chainId, params: ['test'], }) expect(result).toEqual('0x1') expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_chainId, params: ['test'], signature: result, }, ) }) }) describe('eth_accounts', () => { it('should throw an error if no chainId is provided alongside eth_accounts', async () => { expect( provider.request({ method: RequestMethod.eth_accounts, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_accounts', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_accounts, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_accounts request', async () => { portal.address = 'test' const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_accounts, params: ['test'], }) expect(result).toEqual([mockSignedHash]) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_accounts, params: ['test'], signature: result, }, ) }) }) describe('eth_requestAccounts', () => { it('should throw an error if no chainId is provided alongside eth_requestAccounts', async () => { expect( provider.request({ method: RequestMethod.eth_requestAccounts, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_requestAccounts', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_requestAccounts, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_requestAccounts request', async () => { portal.address = 'test' const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_requestAccounts, params: ['test'], }) expect(result).toEqual([mockSignedHash]) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_requestAccounts, params: ['test'], signature: result, }, ) }) }) describe('eth_sendTransaction', () => { it('should throw an error if no chainId is provided alongside eth_sendTransaction', async () => { expect( provider.request({ method: RequestMethod.eth_sendTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_sendTransaction', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_sendTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_sendTransaction request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_sendTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_sendTransaction, params: 'test', rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_sendTransaction, params: ['test'], signature: result, }, ) }) }) describe('eth_signTransaction', () => { it('should throw an error if no chainId is provided alongside eth_signTransaction', async () => { expect( provider.request({ method: RequestMethod.eth_signTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_signTransaction', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_signTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_signTransaction request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTransaction, params: 'test', rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_signTransaction, params: ['test'], signature: result, }, ) }) }) describe('eth_signUserOperation', () => { const mockUserOperation = { sender: '0x1234567890123456789012345678901234567890', callData: '0x', nonce: '0x0', maxFeePerGas: '0x3B9ACA00', maxPriorityFeePerGas: '0x3B9ACA00', signature: '0x', } it('should throw an error if no chainId is provided alongside eth_signUserOperation', async () => { expect( provider.request({ method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_signUserOperation', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_signUserOperation request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], signatureApprovalMemo: 'Test eth_signUserOperation', }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signUserOperation, params: mockUserOperation, rpcUrl: mockRpcUrl, signatureApprovalMemo: 'Test eth_signUserOperation', traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], signature: result, }, ) }) }) describe('eth_sign', () => { it('should throw an error if no chainId is provided alongside eth_sign', async () => { expect( provider.request({ method: RequestMethod.eth_sign, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_sign', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_sign, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_sign request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_sign, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_sign, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_sign, params: ['test'], signature: result, }, ) }) }) describe('eth_signTypedData_v3', () => { it('should throw an error if no chainId is provided alongside eth_signTypedData_v3', async () => { expect( provider.request({ method: RequestMethod.eth_signTypedData_v3, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_signTypedData_v3', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_signTypedData_v3, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_signTypedData_v3 request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v3, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v3, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v3, params: ['test'], signature: result, }, ) }) }) describe('eth_signTypedData_v4', () => { it('should throw an error if no chainId is provided alongside eth_signTypedData_v4', async () => { expect( provider.request({ method: RequestMethod.eth_signTypedData_v4, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside eth_signTypedData_v4', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.eth_signTypedData_v4, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle an eth_signTypedData_v4 request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v4, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v4, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v4, params: ['test'], signature: result, }, ) }) }) describe('personal_sign', () => { it('should throw an error if no chainId is provided alongside personal_sign', async () => { expect( provider.request({ method: RequestMethod.personal_sign, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside personal_sign', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.personal_sign, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "eip155:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle a personal_sign request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.personal_sign, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.personal_sign, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.personal_sign, params: ['test'], signature: result, }, ) }) }) describe('sol_signAndConfirmTransaction', () => { it('should throw an error if no chainId is provided alongside sol_signAndConfirmTransaction', async () => { expect( provider.request({ method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside sol_signAndConfirmTransaction', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle a sol_signAndConfirmTransaction request', async () => { const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], signature: result, }, ) }) }) describe('sol_signAndSendTransaction', () => { it('should throw an error if no chainId is provided alongside sol_signAndSendTransaction', async () => { expect( provider.request({ method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside sol_signAndSendTransaction', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle a sol_signAndSendTransaction request', async () => { const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndSendTransaction, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndSendTransaction, params: ['test'], signature: result, }, ) }) }) describe('sol_signMessage', () => { it('should throw an error if no chainId is provided alongside sol_signMessage', async () => { expect( provider.request({ method: RequestMethod.sol_signMessage, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside sol_signMessage', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.sol_signMessage, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle a sol_signMessage request', async () => { const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signMessage, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signMessage, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signMessage, params: ['test'], signature: result, }, ) }) }) describe('sol_signTransaction', () => { it('should throw an error if no chainId is provided alongside sol_signTransaction', async () => { expect( provider.request({ method: RequestMethod.sol_signTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside sol_signTransaction', async () => { const chainId = 'unsupported:chain' expect( provider.request({ chainId, method: RequestMethod.sol_signTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "solana:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle a sol_signTransaction request', async () => { const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signTransaction, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signTransaction, params: ['test'], signature: result, }, ) }) }) describe('tron_sendTransaction', () => { it('should throw an error if no chainId is provided alongside tron_sendTransaction', async () => { await expect( provider.request({ method: RequestMethod.tron_sendTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID is required for the operation`, ), ) }) it('should throw an error if malformed chainId is provided alongside tron_sendTransaction', async () => { const chainId = 'unsupported:chain' await expect( provider.request({ chainId, method: RequestMethod.tron_sendTransaction, params: ['test'], }), ).rejects.toThrow( new Error( `[PortalProvider] Chain ID must be prefixed with "tron:" for the operation, got ${chainId}`, ), ) }) it('should successfully handle a tron_sendTransaction request', async () => { const result = await provider.request({ chainId: 'tron:nile', method: RequestMethod.tron_sendTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'tron:nile', method: RequestMethod.tron_sendTransaction, params: 'test', rpcUrl: mockRpcUrl, sponsorGas: undefined, traceId: 'mock-trace-id-12345', }) expect(provider.emit).toHaveBeenCalledWith( 'portal_signatureReceived', { chainId: 'tron:nile', method: RequestMethod.tron_sendTransaction, params: ['test'], signature: result, }, ) }) }) }) describe('Signer methods without auto-approval', () => { const mockSigningRequestedHandler = jest.fn() const mockSignatureReceivedHandler = jest.fn() const mockConsoleWarn = jest.spyOn(global.console, 'warn') beforeEach(() => { sdkLogger.configure('warn', console) provider = new Provider({ portal }) portal.autoApprove = false provider.on('portal_signingRequested', mockSigningRequestedHandler) provider.on('portal_signatureReceived', mockSignatureReceivedHandler) }) afterEach(() => { sdkLogger.configure('none') }) describe('eth_chainId', () => { it('should successfully handle an eth_chainId request', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_chainId, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledTimes(0) expect(result).toEqual('0x1') expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_chainId, params: ['test'], signature: result, }) }) }) describe('eth_accounts', () => { it('should successfully handle an eth_accounts request', async () => { portal.address = 'test' const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_accounts, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledTimes(0) expect(result).toEqual([mockSignedHash]) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_accounts, params: ['test'], signature: result, }) }) }) describe('eth_requestAccounts', () => { it('should successfully handle an approved eth_requestAccounts request', async () => { portal.address = 'test' const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_requestAccounts, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledTimes(0) expect(result).toEqual([mockSignedHash]) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_requestAccounts, params: ['test'], signature: result, }) }) }) describe('eth_sendTransaction', () => { it('should successfully handle an approved eth_sendTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.eth_sendTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_sendTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_sendTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_sendTransaction, params: 'test', rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_sendTransaction, params: ['test'], signature: result, }) }) it('should successfully handle a rejected eth_sendTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.eth_sendTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_sendTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_sendTransaction, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'eth_sendTransaction' could not be completed because it was not approved by the user.", ) }) }) describe('eth_signTransaction', () => { it('should successfully handle an approved eth_signTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.eth_signTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_signTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTransaction, params: 'test', rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTransaction, params: ['test'], signature: result, }) }) it('should successfully handle a rejected eth_signTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.eth_signTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_signTransaction, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'eth_signTransaction' could not be completed because it was not approved by the user.", ) }) }) describe('eth_signUserOperation', () => { const mockUserOperation = { sender: '0x1234567890123456789012345678901234567890', callData: '0x', nonce: '0x0', maxFeePerGas: '0x3B9ACA00', maxPriorityFeePerGas: '0x3B9ACA00', signature: '0x', } it('should successfully handle an approved eth_signUserOperation request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], signatureApprovalMemo: 'Test eth_signUserOperation', }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], signatureApprovalMemo: 'Test eth_signUserOperation', }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signUserOperation, params: mockUserOperation, rpcUrl: mockRpcUrl, signatureApprovalMemo: 'Test eth_signUserOperation', traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], signature: result, }) }) it('should successfully handle a rejected eth_signUserOperation request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_signUserOperation, params: [mockUserOperation], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'eth_signUserOperation' could not be completed because it was not approved by the user.", ) }) }) describe('eth_sign', () => { it('should successfully handle an approved eth_sign request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.eth_sign, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_sign, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_sign, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_sign, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_sign, params: ['test'], signature: result, }) }) it('should successfully handle a rejected eth_sign request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.eth_sign, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_sign, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_sign, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'eth_sign' could not be completed because it was not approved by the user.", ) }) }) describe('eth_signTypedData_v3', () => { it('should successfully handle an approved eth_signTypedData_v3 request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.eth_signTypedData_v3, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v3, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_signTypedData_v3, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v3, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v3, params: ['test'], signature: result, }) }) it('should successfully handle a rejected eth_signTypedData_v3 request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.eth_signTypedData_v3, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v3, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_signTypedData_v3, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'eth_signTypedData_v3' could not be completed because it was not approved by the user.", ) }) }) describe('eth_signTypedData_v4', () => { it('should successfully handle an approved eth_signTypedData_v4 request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.eth_signTypedData_v4, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v4, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_signTypedData_v4, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v4, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v4, params: ['test'], signature: result, }) }) it('should successfully handle a rejected eth_signTypedData_v4 request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.eth_signTypedData_v4, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_signTypedData_v4, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.eth_signTypedData_v4, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'eth_signTypedData_v4' could not be completed because it was not approved by the user.", ) }) }) describe('personal_sign', () => { it('should successfully handle an approved personal_sign request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.personal_sign, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.personal_sign, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.personal_sign, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.personal_sign, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'eip155:1', method: RequestMethod.personal_sign, params: ['test'], signature: result, }) }) it('should successfully handle a rejected personal_sign request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.personal_sign, params: ['test'], }) }) const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.personal_sign, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.personal_sign, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'personal_sign' could not be completed because it was not approved by the user.", ) }) }) describe('sol_signAndConfirmTransaction', () => { it('should successfully handle an approved sol_signAndConfirmTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], signature: result, }) }) it('should successfully handle a rejected sol_signAndConfirmTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.sol_signAndConfirmTransaction, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'sol_signAndConfirmTransaction' could not be completed because it was not approved by the user.", ) }) }) describe('sol_signAndSendTransaction', () => { it('should successfully handle an approved sol_signAndSendTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndSendTransaction, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndSendTransaction, params: ['test'], signature: result, }) }) it('should successfully handle a rejected sol_signAndSendTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.sol_signAndSendTransaction, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'sol_signAndSendTransaction' could not be completed because it was not approved by the user.", ) }) }) describe('sol_signMessage', () => { it('should successfully handle an approved sol_signMessage request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.sol_signMessage, params: ['test'], }) }) const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signMessage, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.sol_signMessage, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signMessage, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signMessage, params: ['test'], signature: result, }) }) it('should successfully handle a rejected sol_signMessage request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.sol_signMessage, params: ['test'], }) }) const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signMessage, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.sol_signMessage, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'sol_signMessage' could not be completed because it was not approved by the user.", ) }) }) describe('sol_signTransaction', () => { it('should successfully handle an approved sol_signTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.sol_signTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.sol_signTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signTransaction, params: ['test'], rpcUrl: mockRpcUrl, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signTransaction, params: ['test'], signature: result, }) }) it('should successfully handle a rejected sol_signTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.sol_signTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', method: RequestMethod.sol_signTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.sol_signTransaction, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'sol_signTransaction' could not be completed because it was not approved by the user.", ) }) }) describe('tron_sendTransaction', () => { it('should successfully handle an approved tron_sendTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingApproved', { method: RequestMethod.tron_sendTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'tron:nile', method: RequestMethod.tron_sendTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.tron_sendTransaction, params: ['test'], }) expect(result).toEqual(mockSignedHash) expect(portal.mpc.sign).toHaveBeenCalledWith({ chainId: 'tron:nile', method: RequestMethod.tron_sendTransaction, params: 'test', rpcUrl: mockRpcUrl, sponsorGas: undefined, traceId: 'mock-trace-id-12345', }) expect(mockSignatureReceivedHandler).toHaveBeenCalledWith({ chainId: 'tron:nile', method: RequestMethod.tron_sendTransaction, params: ['test'], signature: result, }) }) it('should successfully handle a rejected tron_sendTransaction request', async () => { provider.on('portal_signingRequested', () => { provider.emit('portal_signingRejected', { method: RequestMethod.tron_sendTransaction, params: ['test'], }) }) const result = await provider.request({ chainId: 'tron:nile', method: RequestMethod.tron_sendTransaction, params: ['test'], }) expect(mockSigningRequestedHandler).toHaveBeenCalledWith({ method: RequestMethod.tron_sendTransaction, params: ['test'], }) expect(result).toEqual(undefined) expect(mockConsoleWarn).toHaveBeenCalledWith( "[PortalProvider] Request for signing method 'tron_sendTransaction' could not be completed because it was not approved by the user.", ) }) }) }) describe('Non-signer methods', () => { beforeAll(() => { portal.autoApprove = true provider.emit = jest.fn() }) it('should successfully handle non-signer method requests', async () => { const result = await provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_sendRawTransaction, params: ['test'], }) expect(result).toEqual('test') expect(global.fetch).toHaveBeenCalledWith(mockRpcUrl, { method: 'POST', headers: expect.objectContaining({ 'Content-Type': 'application/json', [X_PORTAL_TRACE_ID_HEADER]: 'mock-trace-id-12345', }), body: JSON.stringify({ jsonrpc: '2.0', id: '0', method: RequestMethod.eth_sendRawTransaction, params: ['test'], }), }) expect(provider.emit).toHaveBeenCalledWith('portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_sendRawTransaction, params: ['test'], signature: { result: 'test', error: null }, }) }) it('should throw an error if gateway request results in an error', async () => { const errRes = { result: null, error: { code: 4901, data: { key: 'value' } }, } global.fetch = jest.fn().mockResolvedValue({ json: jest.fn().mockResolvedValue(errRes), }) await expect( provider.request({ chainId: 'eip155:1', method: RequestMethod.eth_sendRawTransaction, params: ['test'], }), ).rejects.toThrow(new ProviderRpcError(errRes.error)) expect(provider.emit).toHaveBeenCalledWith('portal_signatureReceived', { chainId: 'eip155:1', method: RequestMethod.eth_sendRawTransaction, params: ['test'], signature: errRes, }) }) it('should throw an error for an unsupported method', async () => { await expect( provider.request({ chainId: 'eip155:1', method: 'wallet_switchEthereumChain' as RequestMethod, params: ['test'], }), ).rejects.toThrow( new ProviderRpcError({ code: RpcErrorCodes.UnsupportedMethod, data: { method: 'wallet_switchEthereumChain' as RequestMethod, params: ['test'], }, }), ) expect(provider.emit).toHaveBeenCalledTimes(0) }) }) }) describe('emit', () => { const provider = new Provider({ portal }) const mockHandler1 = jest.fn() const mockHandler2 = jest.fn() beforeEach(() => { provider.on('test', mockHandler1) provider.on('test', mockHandler2) }) it('should successfully execute all handlers of an event', () => { provider.emit('test', { data: 'test' }) expect(mockHandler1).toHaveBeenCalledWith({ data: 'test' }) expect(mockHandler1).toHaveBeenCalledTimes(1) expect(mockHandler2).toHaveBeenCalledWith({ data: 'test' }) expect(mockHandler2).toHaveBeenCalledTimes(1) }) it('should not execute handlers for a different event', () => { provider.emit('test1', { data: 'test' }) expect(mockHandler1).toHaveBeenCalledTimes(0) expect(mockHandler2).toHaveBeenCalledTimes(0) }) }) describe('on', () => { let provider = new Provider({ portal }) const mockHandler1 = jest.fn() const mockHandler2 = jest.fn() beforeEach(() => { provider = new Provider({ portal }) }) it('should successfully register handlers for an event', () => { const event = 'test' provider.on(event, mockHandler1) provider.on(event, mockHandler2) const eventHandlers = provider.events[event] expect(eventHandlers.length).toEqual(2) expect(eventHandlers).toEqual([ { handler: mockHandler1, once: false }, { handler: mockHandler2, once: false }, ]) }) it('should not register undefined handlers', () => { provider.on('test', undefined as any) const eventHandlers = provider.events['test'] expect(eventHandlers.length).toEqual(0) }) }) describe('removeEventListener', () => { let provider = new Provider({ portal }) const mockHandler1 = jest.fn() const mockHandler2 = jest.fn() beforeEach(() => { provider = new Provider({ portal }) }) it('should successfully remove specified event listener of an event', () => { const event = 'test' provider.on(event, mockHandler1) provider.on(event, mockHandler2) provider.removeEventListener(event, mockHandler1) const eventHandlers = provider.events[event] expect(eventHandlers.length).toEqual(1) expect(eventHandlers).toEqual([{ handler: mockHandler2, once: false }]) }) it('should successfully remove all event listeners of an event if no specific handler is specified to be removed', () => { const event = 'test' provider.on(event, mockHandler1) provider.on(event, mockHandler2) provider.removeEventListener(event) const eventHandlers = provider.events[event] expect(eventHandlers.length).toEqual(0) expect(eventHandlers).toEqual([]) }) it('should skip handler removal for events with no handlers', () => { provider.removeEventListener('non-existent', mockHandler1) const eventHandlers = provider.events['non-existent'] expect(eventHandlers).toEqual(undefined) }) it('should skip handler removal for a handler that does not exist for an event', () => { const event = 'test' provider.on(event, mockHandler1) provider.on(event, mockHandler2) provider.removeEventListener(event, jest.fn()) const eventHandlers = provider.events[event] expect(eventHandlers.length).toEqual(2) expect(eventHandlers).toEqual([ { handler: mockHandler1, once: false }, { handler: mockHandler2, once: false }, ]) }) }) })