/** * @jest-environment jsdom */ import Mpc from '../../mpc' import { EvmAccountType } from '.' import { mockHost, mockGetStatusRequest, mockGetStatusResponse, mockGetAddressesRequest, mockGetAddressesResponse, mockBuildAuthorizationListRequest, mockBuildAuthorizationListResponse, mockBuild7702UpgradeTxRequest, mockBuild7702UpgradeTxResponse, mockUpgradeTo7702Request, } from '../../__mocks/constants' import portalMock from '../../__mocks/portal/portal' import { PortalMpcError } from '../../mpc/errors' describe('EvmAccountType', () => { const mockHostOrigin = `https://${mockHost}` let mpc: Mpc let evmAccountType: EvmAccountType beforeEach(() => { jest.clearAllMocks() portalMock.host = mockHost mpc = new Mpc({ portal: portalMock, }) evmAccountType = new EvmAccountType({ mpc }) }) /******************************* * getStatus Tests *******************************/ describe('getStatus', () => { const args = mockGetStatusRequest const res = mockGetStatusResponse it('should successfully get EVM account type status', (done) => { jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any, origin?) => { const { type, data } = message expect(type).toEqual('portal:evmAccountType:getStatus') expect(data).toMatchObject(args) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getStatusResult', data: res, }, }), ) }) evmAccountType .getStatus(args) .then((data) => { expect(data).toEqual(res) done() }) .catch((_) => { expect(0).toEqual(1) done() }) }) it('should error out if the iframe sends an error message', (done) => { jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementationOnce((message: any, origin?) => { const { type, data } = message expect(type).toEqual('portal:evmAccountType:getStatus') expect(data).toMatchObject(args) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getStatusError', data: { code: 1, message: 'test', }, }, }), ) }) evmAccountType .getStatus(args) .then(() => { expect(0).toEqual(1) done() }) .catch((e) => { expect(e).toBeInstanceOf(PortalMpcError) expect(e.message).toEqual('test') expect(e.code).toEqual(1) done() }) }) }) /******************************* * getAddresses Tests *******************************/ describe('getAddresses', () => { const args = mockGetAddressesRequest const res = mockGetAddressesResponse it('should successfully get EVM addresses', (done) => { jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any, origin?) => { const { type, data } = message expect(type).toEqual('portal:evmAccountType:getAddresses') expect(data).toMatchObject(args) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getAddressesResult', data: res, }, }), ) }) evmAccountType .getAddresses(args) .then((data) => { expect(data).toEqual(res) done() }) .catch((_) => { expect(0).toEqual(1) done() }) }) it('should error out if the iframe sends an error message', (done) => { jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementationOnce((message: any, origin?) => { const { type, data } = message expect(type).toEqual('portal:evmAccountType:getAddresses') expect(data).toMatchObject(args) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getAddressesError', data: { code: 1, message: 'test', }, }, }), ) }) evmAccountType .getAddresses(args) .then((_) => { expect(0).toEqual(1) done() }) .catch((error) => { expect(error).toBeInstanceOf(PortalMpcError) expect(error.message).toEqual('test') expect(error.code).toEqual(1) done() }) }) }) /******************************* * buildAuthorizationList Tests *******************************/ describe('buildAuthorizationList', () => { const args = mockBuildAuthorizationListRequest const res = mockBuildAuthorizationListResponse it('should successfully build authorization list', (done) => { jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any, origin?) => { const { type, data } = message expect(type).toEqual('portal:evmAccountType:buildAuthorizationList') expect(data).toMatchObject(args) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:buildAuthorizationListResult', data: res, }, }), ) }) evmAccountType .buildAuthorizationList(args) .then((data) => { expect(data).toEqual(res) done() }) .catch((_) => { expect(0).toEqual(1) done() }) }) it('should pass subsidize: false when provided', (done) => { const argsWithoutSubsidize = { chain: mockBuildAuthorizationListRequest.chain, subsidize: false, } jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any, origin?) => { const { type, data } = message expect(type).toEqual( 'portal:evmAccountType:buildAuthorizationList', ) expect(data).toMatchObject(argsWithoutSubsidize) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:buildAuthorizationListResult', data: { ...res, metadata: { ...res.metadata, subsidize: false, }, }, }, }), ) }) evmAccountType .buildAuthorizationList(argsWithoutSubsidize) .then((data) => { expect(data.metadata.subsidize).toEqual(false) done() }) .catch((_) => { expect(0).toEqual(1) done() }) }) it('should error out if the iframe sends an error message', (done) => { jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementationOnce((message: any, origin?) => { const { type, data } = message expect(type).toEqual( 'portal:evmAccountType:buildAuthorizationList', ) expect(data).toMatchObject(args) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:buildAuthorizationListError', data: { code: 1, message: 'test', }, }, }), ) }) evmAccountType .buildAuthorizationList(args) .then(() => { expect(0).toEqual(1) done() }) .catch((e) => { expect(e).toBeInstanceOf(PortalMpcError) expect(e.message).toEqual('test') expect(e.code).toEqual(1) done() }) }) }) /******************************* * build7702UpgradeTx Tests *******************************/ describe('build7702UpgradeTx', () => { const args = mockBuild7702UpgradeTxRequest const res = mockBuild7702UpgradeTxResponse it('should successfully build 7702 upgrade transaction', (done) => { jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any, origin?) => { const { type, data } = message expect(type).toEqual('portal:evmAccountType:build7702UpgradeTx') expect(data).toMatchObject(args) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:build7702UpgradeTxResult', data: res, }, }), ) }) evmAccountType .build7702UpgradeTx(args) .then((data) => { expect(data).toEqual(res) done() }) .catch((_) => { expect(0).toEqual(1) done() }) }) it('should pass subsidize: false when provided', (done) => { const argsWithoutSubsidize = { chain: mockBuild7702UpgradeTxRequest.chain, signature: mockBuild7702UpgradeTxRequest.signature, subsidize: false, } jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any, origin?) => { const { type, data } = message expect(type).toEqual( 'portal:evmAccountType:build7702UpgradeTx', ) expect(data).toMatchObject(argsWithoutSubsidize) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:build7702UpgradeTxResult', data: { ...res, metadata: { ...res.metadata, subsidize: false, }, }, }, }), ) }) evmAccountType .build7702UpgradeTx(argsWithoutSubsidize) .then((data) => { expect(data.metadata.subsidize).toEqual(false) done() }) .catch((_) => { expect(0).toEqual(1) done() }) }) it('should error out if the iframe sends an error message', (done) => { jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementationOnce((message: any, origin?) => { const { type, data } = message expect(type).toEqual( 'portal:evmAccountType:build7702UpgradeTx', ) expect(data).toMatchObject(args) expect(typeof message.traceId).toBe('string') expect(origin).toEqual(mockHostOrigin) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:build7702UpgradeTxError', data: { code: 1, message: 'test', }, }, }), ) }) evmAccountType .build7702UpgradeTx(args) .then(() => { expect(0).toEqual(1) done() }) .catch((e) => { expect(e).toBeInstanceOf(PortalMpcError) expect(e.message).toEqual('test') expect(e.code).toEqual(1) done() }) }) }) /******************************* * upgradeTo7702 Tests *******************************/ describe('upgradeTo7702', () => { const args = mockUpgradeTo7702Request it('should successfully upgrade EOA to EIP-7702', (done) => { const mockTxHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any, _origin?) => { const { type, data } = message if (type === 'portal:evmAccountType:getStatus') { expect(data).toMatchObject({ chain: args.chain }) expect(typeof message.traceId).toBe('string') window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getStatusResult', data: mockGetStatusResponse, }, }), ) } else if (type === 'portal:evmAccountType:buildAuthorizationList') { expect(data).toMatchObject({ chain: args.chain, subsidize: true }) expect(typeof message.traceId).toBe('string') window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:buildAuthorizationListResult', data: mockBuildAuthorizationListResponse, }, }), ) } else if (type === 'portal:evmAccountType:build7702UpgradeTx') { expect(data.chain).toEqual(args.chain) expect(data.signature).toBeDefined() expect(data.subsidize).toEqual(true) window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:build7702UpgradeTxResult', data: mockBuild7702UpgradeTxResponse, }, }), ) } }) jest.spyOn(mpc, 'rawSign').mockResolvedValue('0x1234567890abcdef') evmAccountType .upgradeTo7702(args) .then((data) => { expect(data.txHash).toEqual(mockTxHash) done() }) .catch((_) => { expect(0).toEqual(1) done() }) }) it('should throw when account is already upgraded to EIP-7702', (done) => { const alreadyUpgradedStatus = { ...mockGetStatusResponse, data: { status: 'EIP_7702_EOA' as const }, } jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any) => { const { type } = message if (type === 'portal:evmAccountType:getStatus') { window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getStatusResult', data: alreadyUpgradedStatus, }, }), ) } }) evmAccountType .upgradeTo7702(args) .then(() => { expect(0).toEqual(1) done() }) .catch((e) => { expect(e).toBeInstanceOf(Error) expect(e.message).toContain('Account is already upgraded to EIP-7702') done() }) }) it('should throw when chain does not start with eip155:', (done) => { evmAccountType .upgradeTo7702({ chain: 'invalid:1' }) .then(() => { expect(0).toEqual(1) done() }) .catch((e) => { expect(e).toBeInstanceOf(Error) expect(e.message).toContain('Invalid chain for EIP-7702 upgrade') expect(e.message).toContain('invalid:1') done() }) }) it('should throw when account is SMART_CONTRACT', (done) => { const smartContractStatus = { ...mockGetStatusResponse, data: { status: 'SMART_CONTRACT' as const }, } jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any) => { const { type } = message if (type === 'portal:evmAccountType:getStatus') { window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getStatusResult', data: smartContractStatus, }, }), ) } }) evmAccountType .upgradeTo7702(args) .then(() => { expect(0).toEqual(1) done() }) .catch((e) => { expect(e).toBeInstanceOf(Error) expect(e.message).toContain( 'Account is already a smart contract', ) done() }) }) it('should throw when account type is unexpected', (done) => { const unexpectedStatus = { ...mockGetStatusResponse, data: { status: 'UNKNOWN_TYPE' as any }, } jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any) => { const { type } = message if (type === 'portal:evmAccountType:getStatus') { window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getStatusResult', data: unexpectedStatus, }, }), ) } }) evmAccountType .upgradeTo7702(args) .then(() => { expect(0).toEqual(1) done() }) .catch((e) => { expect(e).toBeInstanceOf(Error) expect(e.message).toContain('Unexpected account type') expect(e.message).toContain('UNKNOWN_TYPE') done() }) }) it('should throw when transactionHash is not returned', (done) => { const mockResponseWithoutTxHash = { ...mockBuild7702UpgradeTxResponse, data: { ...mockBuild7702UpgradeTxResponse.data, transactionHash: undefined, }, } jest .spyOn(mpc.iframe?.contentWindow!, 'postMessage') .mockImplementation((message: any, _origin?) => { const { type } = message if (type === 'portal:evmAccountType:getStatus') { window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:getStatusResult', data: mockGetStatusResponse, }, }), ) } else if (type === 'portal:evmAccountType:buildAuthorizationList') { window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:buildAuthorizationListResult', data: mockBuildAuthorizationListResponse, }, }), ) } else if (type === 'portal:evmAccountType:build7702UpgradeTx') { window.dispatchEvent( new MessageEvent('message', { origin: mockHostOrigin, data: { type: 'portal:evmAccountType:build7702UpgradeTxResult', data: mockResponseWithoutTxHash, }, }), ) } }) jest.spyOn(mpc, 'rawSign').mockResolvedValue('0x1234567890abcdef') evmAccountType .upgradeTo7702(args) .then(() => { expect(0).toEqual(1) done() }) .catch((e) => { expect(e).toBeInstanceOf(Error) expect(e.message).toContain('Transaction hash not returned') done() }) }) }) })