UNPKG

@reown/appkit-controllers

Version:

The full stack toolkit to build onchain app UX.

683 lines • 33 kB
import { describe, expect, test, vi } from 'vitest'; import { ConstantsUtil } from '@reown/appkit-common'; import { EnsController } from '../../src/controllers/EnsController.js'; import { WcHelpersUtil } from '../../src/utils/WalletConnectUtil.js'; const mockEthereumNetwork = { id: 1, chainNamespace: ConstantsUtil.CHAIN.EVM, caipNetworkId: 'eip155:1', name: 'Ethereum', nativeCurrency: { name: 'Ethereum', decimals: 18, symbol: 'ETH' }, rpcUrls: { default: { http: ['https://mainnet.infura.io/v3/YOUR-PROJECT-ID'] } } }; const mockPolygonNetwork = { id: 137, chainNamespace: ConstantsUtil.CHAIN.EVM, caipNetworkId: 'eip155:137', name: 'Polygon', nativeCurrency: { name: 'Matic', decimals: 18, symbol: 'MATIC' }, rpcUrls: { default: { http: ['https://polygon.rpc.com'] } } }; const mockSolanaNetwork = { id: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', chainNamespace: ConstantsUtil.CHAIN.SOLANA, caipNetworkId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', name: 'Solana', nativeCurrency: { name: 'Solana', decimals: 9, symbol: 'SOL' }, rpcUrls: { default: { http: ['https://api.mainnet-beta.solana.com'] } } }; const mockSolanaDevnetNetwork = { id: 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1', chainNamespace: ConstantsUtil.CHAIN.SOLANA, caipNetworkId: 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', name: 'Solana', nativeCurrency: { name: 'Solana', decimals: 9, symbol: 'SOL' }, rpcUrls: { default: { http: ['https://api.mainnet-beta.solana.com'] } } }; describe('WcHelpersUtil', () => { describe('getMethodsByChainNamespace', () => { test('returns correct methods for solana', () => { const methods = WcHelpersUtil.getMethodsByChainNamespace('solana'); expect(methods).toEqual([ 'solana_signMessage', 'solana_signTransaction', 'solana_requestAccounts', 'solana_getAccounts', 'solana_signAllTransactions', 'solana_signAndSendTransaction' ]); }); test('returns correct methods for eip155', () => { const methods = WcHelpersUtil.getMethodsByChainNamespace('eip155'); expect(methods).toEqual([ 'eth_accounts', 'eth_requestAccounts', 'eth_sendRawTransaction', 'eth_sign', 'eth_signTransaction', 'eth_signTypedData', 'eth_signTypedData_v3', 'eth_signTypedData_v4', 'eth_sendTransaction', 'personal_sign', 'wallet_switchEthereumChain', 'wallet_addEthereumChain', 'wallet_getPermissions', 'wallet_requestPermissions', 'wallet_registerOnboarding', 'wallet_watchAsset', 'wallet_scanQRCode', // EIP-5792 'wallet_getCallsStatus', 'wallet_showCallsStatus', 'wallet_sendCalls', 'wallet_getCapabilities', // EIP-7715 'wallet_grantPermissions', 'wallet_revokePermissions', //EIP-7811 'wallet_getAssets' ]); }); test('returns empty array for unknown namespace', () => { const methods = WcHelpersUtil.getMethodsByChainNamespace('unknown'); expect(methods).toEqual([]); }); }); describe('createNamespaces', () => { test('creates correct namespaces for multiple chains', () => { const caipNetworks = [ mockEthereumNetwork, mockPolygonNetwork, mockSolanaNetwork, mockSolanaDevnetNetwork ]; const namespaces = WcHelpersUtil.createNamespaces(caipNetworks); expect(namespaces).toEqual({ eip155: { methods: expect.arrayContaining([ 'personal_sign', 'eth_sign', 'eth_signTransaction', 'eth_signTypedData', 'eth_signTypedData_v3', 'eth_signTypedData_v4', 'eth_sendRawTransaction', 'eth_sendTransaction', 'wallet_getCapabilities', 'wallet_sendCalls', 'wallet_showCallsStatus', 'wallet_getCallsStatus', 'wallet_switchEthereumChain' ]), events: ['accountsChanged', 'chainChanged'], chains: ['eip155:1', 'eip155:137'], rpcMap: { '1': 'https://mainnet.infura.io/v3/YOUR-PROJECT-ID', '137': 'https://polygon.rpc.com' } }, solana: { methods: [ 'solana_signMessage', 'solana_signTransaction', 'solana_requestAccounts', 'solana_getAccounts', 'solana_signAllTransactions', 'solana_signAndSendTransaction' ], events: ['accountsChanged', 'chainChanged'], chains: [ 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', 'solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ', 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1', 'solana:8E9rvCKLFQia2Y35HXjjpWzj8weVo44K' ], rpcMap: { '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp': 'https://api.mainnet-beta.solana.com', EtWTRABZaYq6iMfeYKouRu166VU2xqa1: 'https://api.mainnet-beta.solana.com' } } }); }); test('creates empty namespaces for empty input', () => { const namespaces = WcHelpersUtil.createNamespaces([]); expect(namespaces).toEqual({}); }); }); describe('getChainsFromNamespaces', () => { test('returns correct chain ids', () => { const namespaces = { eip155: { methods: [], events: [], chains: ['eip155:1', 'eip155:137'], accounts: ['eip155:4000:0x123', 'eip155:3000:0x456'] }, solana: { methods: [], events: [], chains: ['solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp'], accounts: ['solana:mainnet:address', 'solana:devnet:address'] } }; expect(WcHelpersUtil.getChainsFromNamespaces(namespaces)).toEqual([ 'eip155:1', 'eip155:137', 'eip155:4000', 'eip155:3000', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', 'solana:mainnet', 'solana:devnet' ]); }); }); describe('isSessionEventData', () => { test.each([ [undefined, false], [{}, false], [ { id: 1734112958243866, topic: 'b2cb2748499532d9c307846c444b364dd881c959d9a080e30d63b6a76270a0f8', params: { event: { name: 'accountsChanged', data: ['eip155:1:0x53F31e8972Ebddac1553E37887C25C1b748485A6'] }, chainId: 'eip155:1' } }, true ] ])('should validate session event data', (data, expected) => { expect(WcHelpersUtil.isSessionEventData(data)).toBe(expected); }); }); describe('createDefaultNamespace', () => { test('creates correct namespace structure for solana', () => { const namespace = WcHelpersUtil.createDefaultNamespace('solana'); expect(namespace).toEqual({ methods: [ 'solana_signMessage', 'solana_signTransaction', 'solana_requestAccounts', 'solana_getAccounts', 'solana_signAllTransactions', 'solana_signAndSendTransaction' ], events: ['accountsChanged', 'chainChanged'], chains: [], rpcMap: {} }); }); test('creates correct namespace structure for eip155', () => { const namespace = WcHelpersUtil.createDefaultNamespace('eip155'); expect(namespace).toEqual({ methods: expect.arrayContaining([ 'personal_sign', 'eth_sign', 'eth_signTransaction', 'eth_signTypedData', 'eth_sendTransaction', 'wallet_switchEthereumChain' ]), events: ['accountsChanged', 'chainChanged'], chains: [], rpcMap: {} }); }); test('creates namespace with empty methods for unknown chain namespace', () => { const namespace = WcHelpersUtil.createDefaultNamespace('unknown'); expect(namespace).toEqual({ methods: [], events: ['accountsChanged', 'chainChanged'], chains: [], rpcMap: {} }); }); }); describe('applyNamespaceOverrides', () => { test('returns a copy of baseNamespaces when overrides is undefined', () => { const baseNamespaces = { eip155: { methods: ['eth_sign'], events: ['accountsChanged'], chains: ['eip155:1'], rpcMap: { '1': 'https://ethereum.rpc.com' } } }; const result = WcHelpersUtil.applyNamespaceOverrides(baseNamespaces, undefined); // Should return a copy, not the original object expect(result).not.toBe(baseNamespaces); expect(result).toEqual(baseNamespaces); }); test('handles empty overrides object', () => { const baseNamespaces = { eip155: { methods: ['eth_sign'], events: ['accountsChanged'], chains: ['eip155:1'], rpcMap: { '1': 'https://ethereum.rpc.com' } } }; const result = WcHelpersUtil.applyNamespaceOverrides(baseNamespaces, {}); expect(result).toEqual(baseNamespaces); }); test('creates new namespace that does not exist in base when referenced in multiple override types', () => { const baseNamespaces = { eip155: { methods: ['eth_sign'], events: ['accountsChanged'], chains: ['eip155:1'], rpcMap: { '1': 'https://ethereum.rpc.com' } } }; const result = WcHelpersUtil.applyNamespaceOverrides(baseNamespaces, { methods: { cosmos: ['cosmos_method'] }, chains: { cosmos: ['cosmos:cosmoshub-4'] }, events: { cosmos: ['cosmos_event'] }, rpcMap: { 'cosmos:cosmoshub-4': 'https://cosmos-hub.rpc.com' } }); expect(result['cosmos']).toBeDefined(); expect(result['cosmos']?.methods).toEqual(['cosmos_method']); expect(result['cosmos']?.chains).toEqual(['cosmos:cosmoshub-4']); expect(result['cosmos']?.events).toEqual(['cosmos_event']); expect(result['cosmos']?.rpcMap).toEqual({ 'cosmoshub-4': 'https://cosmos-hub.rpc.com' }); // Original namespace should remain unchanged expect(result['eip155']).toEqual(baseNamespaces['eip155']); }); test('handles missing rpcMap in base namespace when applying rpcMap overrides', () => { const baseNamespaces = { eip155: { methods: ['eth_sign'], events: ['accountsChanged'], chains: ['eip155:1'] } }; const result = WcHelpersUtil.applyNamespaceOverrides(baseNamespaces, { rpcMap: { 'eip155:42': 'https://kovan.rpc.com' } }); expect(result['eip155']?.rpcMap).toEqual({ '42': 'https://kovan.rpc.com' }); }); test('applies method overrides for existing namespace', () => { const baseNamespaces = { eip155: { methods: ['eth_sign', 'personal_sign'], events: ['accountsChanged', 'chainChanged'], chains: ['eip155:1'], rpcMap: { '1': 'https://ethereum.rpc.com' } } }; const result = WcHelpersUtil.applyNamespaceOverrides(baseNamespaces, { methods: { eip155: ['new_method1', 'new_method2'] } }); expect(result['eip155']?.methods).toEqual(['new_method1', 'new_method2']); // Other properties should remain unchanged expect(result['eip155']?.events).toEqual(['accountsChanged', 'chainChanged']); expect(result['eip155']?.chains).toEqual(['eip155:1']); expect(result['eip155']?.rpcMap).toEqual({ '1': 'https://ethereum.rpc.com' }); }); test('applies chain overrides for existing namespace', () => { const baseNamespaces = { eip155: { methods: ['eth_sign', 'personal_sign'], events: ['accountsChanged', 'chainChanged'], chains: ['eip155:1'], rpcMap: { '1': 'https://ethereum.rpc.com' } } }; const result = WcHelpersUtil.applyNamespaceOverrides(baseNamespaces, { chains: { eip155: ['eip155:42', 'eip155:56'] } }); expect(result['eip155']?.chains).toEqual(['eip155:42', 'eip155:56']); // Other properties should remain unchanged expect(result['eip155']?.methods).toEqual(['eth_sign', 'personal_sign']); expect(result['eip155']?.events).toEqual(['accountsChanged', 'chainChanged']); expect(result['eip155']?.rpcMap).toEqual({ '1': 'https://ethereum.rpc.com' }); }); test('applies event overrides for existing namespace', () => { const baseNamespaces = { eip155: { methods: ['eth_sign', 'personal_sign'], events: ['accountsChanged', 'chainChanged'], chains: ['eip155:1'], rpcMap: { '1': 'https://ethereum.rpc.com' } } }; const result = WcHelpersUtil.applyNamespaceOverrides(baseNamespaces, { events: { eip155: ['newEvent1', 'newEvent2'] } }); expect(result['eip155']?.events).toEqual(['newEvent1', 'newEvent2']); // Other properties should remain unchanged expect(result['eip155']?.methods).toEqual(['eth_sign', 'personal_sign']); expect(result['eip155']?.chains).toEqual(['eip155:1']); expect(result['eip155']?.rpcMap).toEqual({ '1': 'https://ethereum.rpc.com' }); }); test('handles multiple types of overrides simultaneously', () => { const baseNamespaces = { eip155: { methods: ['eth_sign', 'personal_sign'], events: ['accountsChanged', 'chainChanged'], chains: ['eip155:1'], rpcMap: { '1': 'https://ethereum.rpc.com' } } }; const result = WcHelpersUtil.applyNamespaceOverrides(baseNamespaces, { methods: { eip155: ['method1', 'method2'] }, chains: { eip155: ['eip155:42'] }, events: { eip155: ['event1'] }, rpcMap: { 'eip155:42': 'https://kovan.rpc.com' } }); expect(result).toEqual({ eip155: { methods: ['method1', 'method2'], events: ['event1'], chains: ['eip155:42'], rpcMap: { '42': 'https://kovan.rpc.com' } } }); }); }); describe('isOriginAllowed', () => { const defaultOrigins = [ 'https://default.com', 'https://*.safe.org', 'https://reown.com/appkit', 'https://demo.reown.com' ]; const allowedPatterns = [ 'https://explicit.com', 'http://localhost:*', 'https://sub.*.example.net' ]; test('should allow exact match from allowedPatterns', () => { expect(WcHelpersUtil.isOriginAllowed('https://explicit.com', allowedPatterns, defaultOrigins)).toBe(true); }); test('should allow exact match from defaultOrigins with full URL', () => { expect(WcHelpersUtil.isOriginAllowed('https://reown.com', allowedPatterns, defaultOrigins)).toBe(true); }); test('should allow exact match from defaultOrigins with sub domain', () => { expect(WcHelpersUtil.isOriginAllowed('https://demo.reown.com', allowedPatterns, defaultOrigins)).toBe(true); }); test('should allow exact match from defaultAllowedOrigins', () => { expect(WcHelpersUtil.isOriginAllowed('https://default.com', allowedPatterns, defaultOrigins)).toBe(true); }); test('should allow wildcard match from allowedPatterns (port)', () => { expect(WcHelpersUtil.isOriginAllowed('http://localhost:3000', allowedPatterns, defaultOrigins)).toBe(true); expect(WcHelpersUtil.isOriginAllowed('http://localhost:8080', allowedPatterns, defaultOrigins)).toBe(true); }); test('should allow wildcard match from defaultAllowedOrigins (subdomain)', () => { expect(WcHelpersUtil.isOriginAllowed('https://app.safe.org', allowedPatterns, defaultOrigins)).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://api.safe.org', allowedPatterns, defaultOrigins)).toBe(true); }); test('should allow wildcard match from allowedPatterns (middle)', () => { expect(WcHelpersUtil.isOriginAllowed('https://sub.domain.example.net', allowedPatterns, defaultOrigins)).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://sub.another-domain.example.net', allowedPatterns, defaultOrigins)).toBe(true); }); test('should deny non-matching origin', () => { expect(WcHelpersUtil.isOriginAllowed('https://unknown.com', allowedPatterns, defaultOrigins)).toBe(false); }); test('should deny partial match without wildcard', () => { expect(WcHelpersUtil.isOriginAllowed('https://explicit.com.hacker', allowedPatterns, defaultOrigins)).toBe(false); expect(WcHelpersUtil.isOriginAllowed('http://safe.org', allowedPatterns, defaultOrigins) // Protocol mismatch ).toBe(false); }); test('should allow if allowed lists are empty (spec: empty allowlist allows all)', () => { expect(WcHelpersUtil.isOriginAllowed('https://any.com', [], [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('http://any.com', [], [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://sub.any.com', [], [])).toBe(true); }); test('should handle origins and patterns with dots correctly', () => { const patternsWithDots = ['https://test.example.com']; const defaultWithDots = ['https://*.another.test.org']; expect(WcHelpersUtil.isOriginAllowed('https://test.example.com', patternsWithDots, defaultWithDots)).toBe(true); // Exact match expect(WcHelpersUtil.isOriginAllowed('https://sub.another.test.org', patternsWithDots, defaultWithDots)).toBe(true); // Wildcard match expect(WcHelpersUtil.isOriginAllowed('https://test-example.com', patternsWithDots, defaultWithDots)).toBe(false); // Dot treated literally expect(WcHelpersUtil.isOriginAllowed('https://sub.another-test.org', patternsWithDots, defaultWithDots)).toBe(false); // Dot treated literally in wildcard part }); test('should be case-sensitive', () => { expect(WcHelpersUtil.isOriginAllowed('HTTPS://EXPLICIT.COM', allowedPatterns, defaultOrigins)).toBe(false); expect(WcHelpersUtil.isOriginAllowed('https://app.SAFE.org', allowedPatterns, defaultOrigins)).toBe(false); }); // Spec-specific coverage test('wildcard should only match a single label (spec: https://*.example.com)', () => { const patterns = ['https://*.example.com']; expect(WcHelpersUtil.isOriginAllowed('https://www.example.com', patterns, [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://example.com', patterns, [])).toBe(false); expect(WcHelpersUtil.isOriginAllowed('https://www.subdomain.example.com', patterns, [])).toBe(false); }); test('multi-label wildcards (spec: https://*.*.example.com)', () => { const patterns = ['https://*.*.example.com']; expect(WcHelpersUtil.isOriginAllowed('https://www.subdomain.example.com', patterns, [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://www.example.com', patterns, [])).toBe(false); expect(WcHelpersUtil.isOriginAllowed('https://example.com', patterns, [])).toBe(false); }); test('partial-label wildcards are invalid (spec: https://www-*.example.com)', () => { const patterns = ['https://www-*.example.com']; expect(WcHelpersUtil.isOriginAllowed('https://www-sub.example.com', patterns, [])).toBe(false); expect(WcHelpersUtil.isOriginAllowed('https://www.example.com', patterns, [])).toBe(false); }); test('schemeless patterns allow http and https (spec: example.com)', () => { const patterns = ['example.com']; expect(WcHelpersUtil.isOriginAllowed('https://example.com', patterns, [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('http://example.com', patterns, [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://www.example.com', patterns, [])).toBe(false); expect(WcHelpersUtil.isOriginAllowed('http://www.example.com', patterns, [])).toBe(false); }); test('scheme-specific patterns must match exactly (spec: https://example.com)', () => { const patterns = ['https://example.com']; expect(WcHelpersUtil.isOriginAllowed('https://example.com', patterns, [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('http://example.com', patterns, [])).toBe(false); }); test('port-specific patterns must match exactly (spec: https://example.com:8080)', () => { const patterns = ['https://example.com:8080']; expect(WcHelpersUtil.isOriginAllowed('https://example.com:8080', patterns, [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://example.com', patterns, [])).toBe(false); expect(WcHelpersUtil.isOriginAllowed('https://example.com:8443', patterns, [])).toBe(false); }); test('localhost and 127.0.0.1 are always permitted regardless of allowlists', () => { expect(WcHelpersUtil.isOriginAllowed('http://localhost:3000', [], [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://localhost:8443', [], [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('http://127.0.0.1:3000', [], [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://127.0.0.1:8443', [], [])).toBe(true); // No explicit port expect(WcHelpersUtil.isOriginAllowed('http://localhost', [], [])).toBe(true); expect(WcHelpersUtil.isOriginAllowed('http://127.0.0.1', [], [])).toBe(true); }); test('should allow 127.0.0.1 IP address with HTTP', () => { expect(WcHelpersUtil.isOriginAllowed('http://127.0.0.1:3000', [], ConstantsUtil.DEFAULT_ALLOWED_ANCESTORS)).toBe(true); expect(WcHelpersUtil.isOriginAllowed('http://127.0.0.1:8080', [], ConstantsUtil.DEFAULT_ALLOWED_ANCESTORS)).toBe(true); }); test('should allow 127.0.0.1 IP address with HTTPS', () => { expect(WcHelpersUtil.isOriginAllowed('https://127.0.0.1:3000', [], ConstantsUtil.DEFAULT_ALLOWED_ANCESTORS)).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://127.0.0.1:8443', [], ConstantsUtil.DEFAULT_ALLOWED_ANCESTORS)).toBe(true); }); test('should allow localhost with HTTPS', () => { expect(WcHelpersUtil.isOriginAllowed('https://localhost:3000', [], ConstantsUtil.DEFAULT_ALLOWED_ANCESTORS)).toBe(true); expect(WcHelpersUtil.isOriginAllowed('https://localhost:8443', [], ConstantsUtil.DEFAULT_ALLOWED_ANCESTORS)).toBe(true); }); }); describe('isUserRejectedRequestError', () => { test('returns true when error.code is USER_REJECTED (5000)', () => { const error = { code: WcHelpersUtil.RPC_ERROR_CODE.USER_REJECTED }; expect(WcHelpersUtil.isUserRejectedRequestError(error)).toBe(true); }); test('returns true when error.code is USER_REJECTED_METHODS (5002)', () => { const error = { code: WcHelpersUtil.RPC_ERROR_CODE.USER_REJECTED_METHODS }; expect(WcHelpersUtil.isUserRejectedRequestError(error)).toBe(true); }); test('returns false for other numeric codes', () => { const error = { code: 1234 }; expect(WcHelpersUtil.isUserRejectedRequestError(error)).toBe(false); }); test('returns false when code is a string number', () => { const error = { code: '5000' }; expect(WcHelpersUtil.isUserRejectedRequestError(error)).toBe(false); }); test('returns false when code is missing', () => { const error = { message: 'Some error' }; expect(WcHelpersUtil.isUserRejectedRequestError(error)).toBe(false); }); test('returns false for null/undefined/non-object inputs', () => { expect(WcHelpersUtil.isUserRejectedRequestError(null)).toBe(false); expect(WcHelpersUtil.isUserRejectedRequestError(undefined)).toBe(false); expect(WcHelpersUtil.isUserRejectedRequestError('string')).toBe(false); expect(WcHelpersUtil.isUserRejectedRequestError(5000)).toBe(false); expect(WcHelpersUtil.isUserRejectedRequestError(true)).toBe(false); expect(WcHelpersUtil.isUserRejectedRequestError(BigInt(0))).toBe(false); expect(WcHelpersUtil.isUserRejectedRequestError(new Error('test'))).toBe(false); }); }); describe('resolveReownName', () => { test('returns first resolved address when available', async () => { vi.spyOn(EnsController, 'resolveName').mockResolvedValue({ addresses: { eip155: { address: '0xabc' }, solana: { address: 'SoLAddRess' } } }); await expect(WcHelpersUtil.resolveReownName('alice.reown')).resolves.toBe('0xabc'); }); test('returns false when no address is found', async () => { vi.spyOn(EnsController, 'resolveName').mockResolvedValue({ addresses: {} }); await expect(WcHelpersUtil.resolveReownName('bob.reown')).resolves.toBe(false); vi.spyOn(EnsController, 'resolveName').mockResolvedValue(undefined); await expect(WcHelpersUtil.resolveReownName('charlie.reown')).resolves.toBe(false); }); }); describe('getWalletConnectAccounts', () => { test('returns parsed unique accounts for namespace', () => { const provider = { session: { namespaces: { eip155: { accounts: [ 'eip155:1:0xABCDEF0000000000000000000000000000000001', 'eip155:137:0xabcdef0000000000000000000000000000000001', // duplicate address, different chain, different case 'eip155:1:0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB' ] } } } }; const result = WcHelpersUtil.getWalletConnectAccounts(provider, 'eip155'); expect(result).toEqual([ { address: '0xABCDEF0000000000000000000000000000000001', chainId: '1', chainNamespace: 'eip155' }, { address: '0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', chainId: '1', chainNamespace: 'eip155' } ]); }); test('returns empty array when no accounts found', () => { const provider = { session: { namespaces: { eip155: { accounts: [] } } } }; expect(WcHelpersUtil.getWalletConnectAccounts(provider, 'eip155')).toEqual([]); }); }); describe('listenWcProvider', () => { function createMockProvider() { const handlers = {}; return { on(event, handler) { handlers[event] = handlers[event] || []; handlers[event].push(handler); }, emit(event, ...args) { ; (handlers[event] || []).forEach(fn => fn(...args)); }, // minimal shapes used by listeners session: { namespaces: { eip155: { accounts: ['eip155:1:0x111'] } } }, rpcProviders: { eip155: { getDefaultChain: () => '1' } } }; } test('wires connect/disconnect/chainChanged/display_uri events', () => { const provider = createMockProvider(); const onConnect = vi.fn(); const onDisconnect = vi.fn(); const onChainChanged = vi.fn(); const onDisplayUri = vi.fn(); const spyAccounts = vi .spyOn(WcHelpersUtil, 'getWalletConnectAccounts') .mockReturnValue([{ address: '0x1', chainId: '1', chainNamespace: 'eip155' }]); WcHelpersUtil.listenWcProvider({ universalProvider: provider, namespace: 'eip155', onConnect, onDisconnect, onChainChanged, onDisplayUri }); provider.emit('connect'); expect(onConnect).toHaveBeenCalledWith([ { address: '0x1', chainId: '1', chainNamespace: 'eip155' } ]); spyAccounts.mockRestore(); provider.emit('disconnect'); expect(onDisconnect).toHaveBeenCalled(); provider.emit('chainChanged', 1); expect(onChainChanged).toHaveBeenCalledWith(1); provider.emit('display_uri', 'wc:abc'); expect(onDisplayUri).toHaveBeenCalledWith('wc:abc'); }); test('accountsChanged emits only when parsed accounts exist', () => { const provider = createMockProvider(); const onAccountsChanged = vi.fn(); WcHelpersUtil.listenWcProvider({ universalProvider: provider, namespace: 'eip155', onAccountsChanged }); // matching account present provider.emit('accountsChanged', ['0x111']); expect(onAccountsChanged).toHaveBeenCalledWith([ { address: '0x111', chainId: '1', chainNamespace: 'eip155' } ]); // non-matching should not emit onAccountsChanged.mockClear(); provider.emit('accountsChanged', ['0x222']); expect(onAccountsChanged).not.toHaveBeenCalled(); }); }); }); //# sourceMappingURL=WalletConnectUtil.test.js.map