UNPKG

@reown/appkit-controllers

Version:

#### 🔗 [Website](https://reown.com/appkit)

386 lines • 17.6 kB
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { ConstantsUtil } from '@reown/appkit-common'; import { ConnectorController } from '../../exports'; import { AccountController } from '../../src/controllers/AccountController'; import { BlockchainApiController } from '../../src/controllers/BlockchainApiController'; import { ChainController } from '../../src/controllers/ChainController'; import { ConnectionController } from '../../src/controllers/ConnectionController'; import { BalanceUtil } from '../../src/utils/BalanceUtil'; import { ERC7811Utils } from '../../src/utils/ERC7811Util'; import { StorageUtil } from '../../src/utils/StorageUtil'; vi.mock('../../src/controllers/AccountController'); vi.mock('../../src/controllers/BlockchainApiController'); vi.mock('../../src/controllers/ChainController'); vi.mock('../../src/controllers/ConnectionController'); vi.mock('../../src/utils/ERC7811Util'); vi.mock('../../src/utils/StorageUtil'); const mockEthereumNetwork = { id: '1', chainNamespace: 'eip155', caipNetworkId: 'eip155:1', name: 'Ethereum', nativeCurrency: { name: 'Ethereum', decimals: 18, symbol: 'ETH' }, rpcUrls: { default: { http: ['https://mainnet.infura.io/v3/YOUR-PROJECT-ID'] } } }; const mockSolanaNetwork = { id: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', caipNetworkId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', chainNamespace: 'solana', name: 'Solana', nativeCurrency: { name: 'Solana', decimals: 9, symbol: 'SOL' }, rpcUrls: { default: { http: ['https://api.mainnet-beta.solana.com'] } } }; const mockEthereumAddress = '0x1234567890123456789012345678901234567890'; const mockEthChainIdAsHex = '0x1'; describe('BalanceUtil', () => { beforeEach(() => { vi.resetAllMocks(); vi.spyOn(ConnectorController, 'getConnectorId').mockReturnValue(ConstantsUtil.CONNECTOR_ID.AUTH); }); afterEach(() => { vi.resetAllMocks(); }); describe('getMyTokensWithBalance', () => { beforeEach(() => { AccountController.state.address = mockEthereumAddress; ChainController.state.activeCaipNetwork = mockEthereumNetwork; vi.mocked(ERC7811Utils.getChainIdHexFromCAIP2ChainId).mockReturnValue(mockEthChainIdAsHex); ConnectorController.state.activeConnectorIds = { eip155: ConstantsUtil.CONNECTOR_ID.AUTH, solana: undefined, polkadot: undefined, bip122: undefined, cosmos: undefined }; vi.mocked(StorageUtil.getBalanceCacheForCaipAddress).mockReturnValue(undefined); }); afterEach(() => { vi.clearAllMocks(); }); it('should return empty array when address is missing', async () => { AccountController.state.address = undefined; const result = await BalanceUtil.getMyTokensWithBalance(); expect(result).toEqual([]); }); it('should return empty array when network is missing', async () => { ChainController.state.activeCaipNetwork = undefined; const result = await BalanceUtil.getMyTokensWithBalance(); expect(result).toEqual([]); }); it('should use ERC7811 for EIP155 chain when wallet_getAssets is available', async () => { const mockAssetsResponse = { [mockEthChainIdAsHex]: [ { address: mockEthereumAddress, balance: '0xDE0B6B3A7640000', type: 'NATIVE', metadata: { name: 'Ethereum', symbol: 'ETH', decimals: 18, value: 0.0001, price: 3200, iconUrl: 'https://example.com/icon.png' } } ] }; AccountController.state.address = mockEthereumAddress; ChainController.state.activeCaipNetwork = mockEthereumNetwork; const mockBalance = { symbol: 'ETH', quantity: { decimals: '18', numeric: '0.0001' }, name: 'Ethereum', chainId: mockEthChainIdAsHex, price: 3200, iconUrl: 'https://example.com/icon.png' }; vi.mocked(ConnectionController.getCapabilities).mockResolvedValue({ [mockEthChainIdAsHex]: { assetDiscovery: { supported: true } } }); vi.mocked(ConnectionController.walletGetAssets).mockResolvedValue(mockAssetsResponse); vi.mocked(ERC7811Utils.isWalletGetAssetsResponse).mockReturnValue(true); vi.mocked(ERC7811Utils.createBalance).mockReturnValue(mockBalance); const result = await BalanceUtil.getMyTokensWithBalance(); expect(ConnectionController.walletGetAssets).toHaveBeenCalledWith({ account: mockEthereumAddress, chainFilter: [mockEthChainIdAsHex] }); expect(result).toEqual([mockBalance]); }); it('should use BlockchainApi for Solana chain', async () => { const mockSolanaAddress = 'solana123'; const mockBalances = [ { symbol: 'SOL', quantity: { decimals: '9', numeric: '1.0' }, name: 'Solana', chainId: '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', price: 0, iconUrl: '' } ]; AccountController.state.address = mockSolanaAddress; ChainController.state.activeCaipNetwork = mockSolanaNetwork; vi.mocked(BlockchainApiController.getBalance).mockResolvedValue({ balances: mockBalances }); const result = await BalanceUtil.getMyTokensWithBalance(); expect(BlockchainApiController.getBalance).toHaveBeenCalledWith(mockSolanaAddress, mockSolanaNetwork.caipNetworkId, undefined); expect(result).toEqual(mockBalances); }); it('should use BlockchainApi if connector is not auth', async () => { const mockBalances = [ { symbol: 'ETH', quantity: { decimals: '18', numeric: '1.0' }, name: 'Ethereum', chainId: mockEthChainIdAsHex, price: 0, iconUrl: '' } ]; AccountController.state.address = mockEthereumAddress; ChainController.state.activeCaipNetwork = mockEthereumNetwork; vi.spyOn(ConnectorController, 'getConnectorId').mockReturnValue(ConstantsUtil.CONNECTOR_ID.INJECTED); vi.mocked(BlockchainApiController.getBalance).mockResolvedValue({ balances: mockBalances }); const result = await BalanceUtil.getMyTokensWithBalance(); expect(BlockchainApiController.getBalance).toHaveBeenCalledWith(mockEthereumAddress, mockEthereumNetwork.caipNetworkId, undefined); expect(result).toEqual(mockBalances); }); it('should use BlockchainApi when wallet_getAssets is not available', async () => { const mockBalances = [ { symbol: 'ETH', quantity: { decimals: '18', numeric: '1.0' }, name: 'Ethereum', chainId: mockEthChainIdAsHex, price: 0, iconUrl: '' } ]; vi.mocked(ConnectionController.getCapabilities).mockResolvedValue({}); vi.mocked(BlockchainApiController.getBalance).mockResolvedValue({ balances: mockBalances }); const result = await BalanceUtil.getMyTokensWithBalance(); expect(BlockchainApiController.getBalance).toHaveBeenCalledWith(mockEthereumAddress, mockEthereumNetwork.caipNetworkId, undefined); expect(result).toEqual(mockBalances); }); it('should filter out zero decimal balances', async () => { const mockBalances = [ { symbol: 'ETH', quantity: { decimals: '18', numeric: '1.0' }, name: 'Ethereum', chainId: mockEthChainIdAsHex, price: 0, iconUrl: '' }, { symbol: 'TEST', quantity: { decimals: '0', numeric: '0' }, name: 'Test Token', chainId: mockEthChainIdAsHex, price: 0, iconUrl: '' } ]; vi.mocked(BlockchainApiController.getBalance).mockResolvedValue({ balances: mockBalances }); const result = await BalanceUtil.getMyTokensWithBalance(); expect(result).toEqual([mockBalances[0]]); }); it('should return cached balance from storage if it exists', async () => { const mockCachedBalance = { balances: [ { symbol: 'ETH', quantity: { decimals: '18', numeric: '1.0' }, name: 'Ethereum', chainId: mockEthChainIdAsHex, price: 3200, iconUrl: 'https://example.com/icon.png' } ], timestamp: Date.now() }; vi.mocked(StorageUtil.getBalanceCacheForCaipAddress).mockReturnValue(mockCachedBalance); const result = await BalanceUtil.getMyTokensWithBalance(); expect(StorageUtil.getBalanceCacheForCaipAddress).toHaveBeenCalledWith(`${mockEthereumNetwork.caipNetworkId}:${mockEthereumAddress}`); expect(result).toEqual(mockCachedBalance.balances); expect(ConnectionController.walletGetAssets).not.toHaveBeenCalled(); expect(BlockchainApiController.getBalance).not.toHaveBeenCalled(); }); }); describe('getEIP155Balances', () => { beforeEach(() => { AccountController.state.address = mockEthereumAddress; ChainController.state.activeCaipNetwork = mockEthereumNetwork; ConnectorController.state.activeConnectorIds = { eip155: ConstantsUtil.CONNECTOR_ID.AUTH, solana: undefined, polkadot: undefined, bip122: undefined, cosmos: undefined }; vi.mocked(ERC7811Utils.getChainIdHexFromCAIP2ChainId).mockReturnValue(mockEthChainIdAsHex); }); afterEach(() => { vi.clearAllMocks(); }); it('should return null when walletGetAssetsResponse is invalid', async () => { const invalidResponse = { invalid: 'response' }; vi.mocked(ConnectionController.getCapabilities).mockResolvedValue({ [mockEthChainIdAsHex]: { assetDiscovery: { supported: true } } }); vi.mocked(ConnectionController.walletGetAssets).mockResolvedValue(invalidResponse); vi.mocked(ERC7811Utils.isWalletGetAssetsResponse).mockReturnValue(false); // Mock the type guard to return false const result = await BalanceUtil.getEIP155Balances(mockEthereumAddress, mockEthereumNetwork); expect(result).toBeNull(); }); it('should return null when asset discovery fails', async () => { const errorMessage = 'Network error'; vi.mocked(ConnectionController.getCapabilities).mockResolvedValue({ [mockEthChainIdAsHex]: { assetDiscovery: { supported: true } } }); vi.mocked(ConnectionController.walletGetAssets).mockRejectedValue(new Error(errorMessage)); const result = await BalanceUtil.getEIP155Balances(mockEthereumAddress, mockEthereumNetwork); expect(result).toBeNull(); }); it('should return assets when walletGetAssetsResponse contains chainIdHex', async () => { const mockAssetsResponse = { [mockEthChainIdAsHex]: [ { address: mockEthereumAddress, balance: '0xDE0B6B3A7640000', type: 'NATIVE', metadata: { name: 'Ethereum', symbol: 'ETH', decimals: 18, value: 0.0001, price: 3200, iconUrl: 'https://example.com/icon.png' } } ] }; vi.mocked(ConnectionController.getCapabilities).mockResolvedValue({ [mockEthChainIdAsHex]: { assetDiscovery: { supported: true } } }); vi.mocked(ConnectionController.walletGetAssets).mockResolvedValue(mockAssetsResponse); vi.mocked(ERC7811Utils.isWalletGetAssetsResponse).mockReturnValue(true); // Mock the type guard to return true vi.mocked(ERC7811Utils.createBalance).mockReturnValue({ symbol: 'ETH', quantity: { decimals: '18', numeric: '0.0001' }, name: 'Ethereum', chainId: mockEthChainIdAsHex, price: 3200, iconUrl: 'https://example.com/icon.png' }); const result = await BalanceUtil.getEIP155Balances(mockEthereumAddress, mockEthereumNetwork); expect(result).toBeDefined(); expect(result).toHaveLength(1); expect(result).not.toBeNull(); if (result) { expect(result[0]).toMatchObject({ symbol: 'ETH', quantity: { decimals: '18', numeric: '0.0001' }, name: 'Ethereum', chainId: mockEthChainIdAsHex, price: 3200, iconUrl: 'https://example.com/icon.png' }); } }); it('should return an empty array when walletGetAssetsResponse does not contain chainIdHex', async () => { const mockAssetsResponse = { '0x2': [] }; vi.mocked(ConnectionController.getCapabilities).mockResolvedValue({ [mockEthChainIdAsHex]: { assetDiscovery: { supported: true } } }); vi.mocked(ConnectionController.walletGetAssets).mockResolvedValue(mockAssetsResponse); vi.mocked(ERC7811Utils.isWalletGetAssetsResponse).mockReturnValue(true); const result = await BalanceUtil.getEIP155Balances(mockEthereumAddress, mockEthereumNetwork); expect(result).toEqual([]); }); it('should save balance to storage after fetching', async () => { const mockAssetsResponse = { [mockEthChainIdAsHex]: [ { address: mockEthereumAddress, balance: '0xDE0B6B3A7640000', type: 'NATIVE', metadata: { name: 'Ethereum', symbol: 'ETH', decimals: 18, value: 0.0001, price: 3200, iconUrl: 'https://example.com/icon.png' } } ] }; const mockBalance = { symbol: 'ETH', quantity: { decimals: '18', numeric: '0.0001' }, name: 'Ethereum', chainId: mockEthChainIdAsHex, price: 3200, iconUrl: 'https://example.com/icon.png' }; vi.mocked(ConnectionController.getCapabilities).mockResolvedValue({ [mockEthChainIdAsHex]: { assetDiscovery: { supported: true } } }); vi.mocked(ConnectionController.walletGetAssets).mockResolvedValue(mockAssetsResponse); vi.mocked(ERC7811Utils.isWalletGetAssetsResponse).mockReturnValue(true); vi.mocked(ERC7811Utils.createBalance).mockReturnValue(mockBalance); const result = await BalanceUtil.getEIP155Balances(mockEthereumAddress, mockEthereumNetwork); expect(StorageUtil.updateBalanceCache).toHaveBeenCalledWith({ caipAddress: `${mockEthereumNetwork.caipNetworkId}:${mockEthereumAddress}`, balance: { balances: [mockBalance] }, timestamp: expect.any(Number) }); expect(result).toEqual([mockBalance]); }); }); }); //# sourceMappingURL=BalanceUtil.test.js.map