diff --git a/src/background/services/network/NetworkService.test.ts b/src/background/services/network/NetworkService.test.ts index 11bb456f..b8e0b337 100644 --- a/src/background/services/network/NetworkService.test.ts +++ b/src/background/services/network/NetworkService.test.ts @@ -103,6 +103,38 @@ describe('background/services/network/NetworkService', () => { const avaxMainnet = mockNetwork(NetworkVMType.EVM, false, { chainId: 43114, }); + const leetNetwork = mockNetwork(NetworkVMType.EVM, false, { + chainId: 1337, + }); + const btcNetwork = mockNetwork(NetworkVMType.BITCOIN, false, { + chainId: ChainId.BITCOIN, + }); + const sepolia = mockNetwork(NetworkVMType.EVM, true, { + chainId: 11155111, + }); + + const mockChainList = (instance: NetworkService) => { + // eslint-disable-next-line + // @ts-ignore + jest.spyOn(instance._rawNetworks, 'promisify').mockResolvedValue( + Promise.resolve({ + [ethMainnet.chainId]: ethMainnet, + [avaxMainnet.chainId]: avaxMainnet, + [btcNetwork.chainId]: btcNetwork, + [leetNetwork.chainId]: leetNetwork, + [sepolia.chainId]: sepolia, + }) + ); + jest.spyOn(instance.allNetworks, 'promisify').mockResolvedValue( + Promise.resolve({ + [ethMainnet.chainId]: ethMainnet, + [avaxMainnet.chainId]: avaxMainnet, + [btcNetwork.chainId]: btcNetwork, + [leetNetwork.chainId]: leetNetwork, + [sepolia.chainId]: sepolia, + }) + ); + }; beforeAll(() => { process.env = { @@ -115,27 +147,10 @@ describe('background/services/network/NetworkService', () => { beforeEach(() => { jest.resetAllMocks(); - jest.mocked(getChainsAndTokens).mockResolvedValue({ - [43114]: { - chainName: 'test chain', - chainId: 123, - vmName: NetworkVMType.EVM, - rpcUrl: 'https://rpcurl.example', - explorerUrl: 'https://explorer.url', - networkToken: { - name: 'test network token', - symbol: 'TNT', - description: '', - decimals: 18, - logoUri: '', - }, - logoUri: '', - primaryColor: 'blue', - isTestnet: false, - }, - }); + jest.mocked(getChainsAndTokens).mockResolvedValue({}); jest.mocked(FetchRequest).mockImplementation((url) => ({ url } as any)); + mockChainList(service); }); afterAll(() => { @@ -143,18 +158,12 @@ describe('background/services/network/NetworkService', () => { }); describe('.getInitialNetworkForDapp()', () => { - const leetNetwork = mockNetwork(NetworkVMType.EVM, false, { - chainId: 1337, - }); - const btcNetwork = mockNetwork(NetworkVMType.BITCOIN, false, { - chainId: ChainId.BITCOIN, - }); - const chainlist = { [ethMainnet.chainId]: ethMainnet, [avaxMainnet.chainId]: avaxMainnet, [leetNetwork.chainId]: leetNetwork, [btcNetwork.chainId]: btcNetwork, + [sepolia.chainId]: sepolia, }; it('returns the most recently active network for given domain', async () => { @@ -181,7 +190,7 @@ describe('background/services/network/NetworkService', () => { .mockResolvedValueOnce(chainlist); await service.init(); - await service.setNetwork(runtime.id, leetNetwork); + await service.setNetwork(runtime.id, leetNetwork.caipId); const network = await service.getInitialNetworkForDapp('test.app'); @@ -196,7 +205,7 @@ describe('background/services/network/NetworkService', () => { .mockResolvedValueOnce(chainlist); await service.init(); - await service.setNetwork(runtime.id, btcNetwork); + await service.setNetwork(runtime.id, btcNetwork.caipId); const network = await service.getInitialNetworkForDapp('core.app'); @@ -211,7 +220,7 @@ describe('background/services/network/NetworkService', () => { .mockResolvedValueOnce(chainlist); await service.init(); - await service.setNetwork(runtime.id, btcNetwork); + await service.setNetwork(runtime.id, btcNetwork.caipId); const network = await service.getInitialNetworkForDapp('test.app'); @@ -241,7 +250,7 @@ describe('background/services/network/NetworkService', () => { }); it('persists current network for given domain', async () => { - await service.setNetwork('test.app', ethMainnet); + await service.setNetwork('test.app', ethMainnet.caipId); expect(storageServiceMock.save).toHaveBeenCalledWith( NETWORK_STORAGE_KEY, @@ -258,7 +267,7 @@ describe('background/services/network/NetworkService', () => { service.dappScopeChanged.addOnce(listener); - await service.setNetwork('test.app', ethMainnet); + await service.setNetwork('test.app', ethMainnet.caipId); expect(listener).toHaveBeenCalledWith({ domain: 'test.app', @@ -267,17 +276,14 @@ describe('background/services/network/NetworkService', () => { }); describe('when dApp network switch results in environment change', () => { - const sepolia = mockNetwork(NetworkVMType.EVM, true, { - chainId: 11155111, - }); - beforeEach(async () => { service = new NetworkService( storageServiceMock, featureFlagsServiceMock ); + mockChainList(service); // Set Ethereum Mainnet directly for the frontend - await service.setNetwork(runtime.id, ethMainnet); + await service.setNetwork(runtime.id, ethMainnet.caipId); expect(service.uiActiveNetwork).toEqual(ethMainnet); }); @@ -285,7 +291,7 @@ describe('background/services/network/NetworkService', () => { storageServiceMock.load.mockResolvedValueOnce({ [runtime.id]: sepolia.caipId, }); - await service.setNetwork('app.uniswap.io', sepolia); + await service.setNetwork('app.uniswap.io', sepolia.caipId); expect(storageServiceMock.save).toHaveBeenCalledWith( NETWORK_STORAGE_KEY, expect.objectContaining({ @@ -300,7 +306,7 @@ describe('background/services/network/NetworkService', () => { storageServiceMock.load.mockResolvedValueOnce({ [runtime.id]: sepolia.caipId, }); - await service.setNetwork('app.uniswap.io', sepolia); + await service.setNetwork('app.uniswap.io', sepolia.caipId); expect(service.uiActiveNetwork).toEqual(sepolia); expect(storageServiceMock.save).toHaveBeenCalledWith( NETWORK_STORAGE_KEY, @@ -318,7 +324,7 @@ describe('background/services/network/NetworkService', () => { service.developerModeChanged.addOnce(onDevModeChange); - await service.setNetwork('app.uniswap.io', sepolia); + await service.setNetwork('app.uniswap.io', sepolia.caipId); expect(service.uiActiveNetwork).toEqual(sepolia); expect(onDevModeChange).toHaveBeenCalledWith(true); @@ -327,7 +333,7 @@ describe('background/services/network/NetworkService', () => { describe('when changing network for synchronized dApps', () => { it('uses the extension ID as domain', async () => { - await service.setNetwork('core.app', ethMainnet); + await service.setNetwork('core.app', ethMainnet.caipId); expect(storageServiceMock.save).toHaveBeenCalledWith( NETWORK_STORAGE_KEY, @@ -340,12 +346,18 @@ describe('background/services/network/NetworkService', () => { }); it('changes the network for the extension ui as well', async () => { + jest.spyOn(service.allNetworks, 'promisify').mockResolvedValue( + Promise.resolve({ + [ethMainnet.chainId]: ethMainnet, + [avaxMainnet.chainId]: avaxMainnet, + }) + ); // Set Ethereum directly for the frontend - await service.setNetwork(runtime.id, ethMainnet); + await service.setNetwork(runtime.id, ethMainnet.caipId); expect(service.uiActiveNetwork).toEqual(ethMainnet); // Set C-Chain for core.app and expect it to change also for the extension UI - await service.setNetwork('core.app', avaxMainnet); + await service.setNetwork('core.app', avaxMainnet.caipId); expect(service.uiActiveNetwork).toEqual(avaxMainnet); }); @@ -353,8 +365,13 @@ describe('background/services/network/NetworkService', () => { const listener = jest.fn(); service.dappScopeChanged.addOnce(listener); + jest.spyOn(service.allNetworks, 'promisify').mockResolvedValue( + Promise.resolve({ + [ethMainnet.chainId]: ethMainnet, + }) + ); - await service.setNetwork('core.app', ethMainnet); + await service.setNetwork('core.app', ethMainnet.caipId); expect(listener).toHaveBeenCalledWith({ domain: runtime.id, @@ -373,6 +390,7 @@ describe('background/services/network/NetworkService', () => { it('saves custom RPC headers', async () => { const overrides = { chainId: 1337, + caipId: 'eip155:1337', customRpcHeaders: { 'X-Glacier-Api-Key': 'my-elite-api-key', }, @@ -415,8 +433,13 @@ describe('background/services/network/NetworkService', () => { jest.spyOn(networkService._rawNetworks, 'promisify').mockResolvedValue({ 1337: activeNetwork, }); + jest.spyOn(networkService.allNetworks, 'promisify').mockResolvedValue( + Promise.resolve({ + 1337: activeNetwork, + }) + ); - await networkService.setNetwork(runtime.id, activeNetwork); + await networkService.setNetwork(runtime.id, activeNetwork.caipId); await networkService.updateNetworkOverrides({ caipId: 'eip155:1337', @@ -437,24 +460,25 @@ describe('background/services/network/NetworkService', () => { describe('saveCustomNetwork()', () => { let customNetwork; - beforeEach(() => { + beforeEach(async () => { customNetwork = mockNetwork(NetworkVMType.EVM, false); }); it('should throw an error because of the chainlist failed to load', async () => { jest // eslint-disable-next-line - // @ts-expect-error + // @ts-ignore .spyOn(service._rawNetworks, 'promisify') - .mockResolvedValue(Promise.resolve(undefined)); - expect(service.saveCustomNetwork(customNetwork)).rejects.toThrow( + .mockResolvedValueOnce(Promise.resolve(undefined)); + + await expect(service.saveCustomNetwork(customNetwork)).rejects.toThrow( 'chainlist failed to load' ); }); it('should throw an error because of duplicated ID', async () => { const newCustomNetwork = { ...customNetwork, chainId: 43114 }; - expect(service.saveCustomNetwork(newCustomNetwork)).rejects.toThrow( + await expect(service.saveCustomNetwork(newCustomNetwork)).rejects.toThrow( 'chain ID already exists' ); }); @@ -476,11 +500,8 @@ describe('background/services/network/NetworkService', () => { ...customNetwork, rpcUrl: newRpcUrl, }; - await service.saveCustomNetwork(newCustomNetwork); - - // Set it as active - const savedActiveNetwork = await service.getNetwork( - newCustomNetwork.chainId + const savedActiveNetwork = await service.saveCustomNetwork( + newCustomNetwork ); expect(savedActiveNetwork?.rpcUrl).toBe(newRpcUrl); diff --git a/src/background/services/network/NetworkService.ts b/src/background/services/network/NetworkService.ts index 2e80240a..8680a17c 100644 --- a/src/background/services/network/NetworkService.ts +++ b/src/background/services/network/NetworkService.ts @@ -149,18 +149,25 @@ export class NetworkService implements OnLock, OnStorageReady { ); } - async setNetwork(domain: string, selectedNetwork: NetworkWithCaipId) { + async setNetwork(domain: string, caipId: string) { const isSynced = isSyncDomain(domain); + // For supported networks, use config from saved chainlist + // instead of relying on payload that may come from a 3rd party: + const targetNetwork = await this.getNetwork(caipId); + if (!targetNetwork) { + throw new Error(`Network not found: ${caipId}`); + } + const changesEnvironment = Boolean(this._uiActiveNetwork?.isTestnet) !== - Boolean(selectedNetwork.isTestnet); + Boolean(targetNetwork.isTestnet); if (isSynced || changesEnvironment) { - this.uiActiveNetwork = selectedNetwork; + this.uiActiveNetwork = targetNetwork; } // Save scope for requesting dApp - await this.#updateDappScopes({ [domain]: selectedNetwork.caipId }); + await this.#updateDappScopes({ [domain]: targetNetwork.caipId }); // If change resulted in an environment switch, also notify other dApps if (changesEnvironment) { @@ -173,7 +180,7 @@ export class NetworkService implements OnLock, OnStorageReady { .map(([savedDomain]) => [ savedDomain, chainIdToCaip( - selectedNetwork.isTestnet + targetNetwork.isTestnet ? ChainId.AVALANCHE_TESTNET_ID : ChainId.AVALANCHE_MAINNET_ID ), @@ -679,7 +686,7 @@ export class NetworkService implements OnLock, OnStorageReady { ); if (network) { - await this.setNetwork(runtime.id, network); + await this.setNetwork(runtime.id, network.caipId); } } diff --git a/src/background/services/network/handlers/avalanche_setDeveloperMode.ts b/src/background/services/network/handlers/avalanche_setDeveloperMode.ts index 83b81df7..c89e7948 100644 --- a/src/background/services/network/handlers/avalanche_setDeveloperMode.ts +++ b/src/background/services/network/handlers/avalanche_setDeveloperMode.ts @@ -7,6 +7,7 @@ import { NetworkService } from '../NetworkService'; import { ethErrors } from 'eth-rpc-errors'; import { openApprovalWindow } from '@src/background/runtime/openApprovalWindow'; import { ChainId } from '@avalabs/core-chains-sdk'; +import { chainIdToCaip } from '@src/utils/caipConversion'; @injectable() export class AvalancheSetDeveloperModeHandler extends DAppRequestHandler { @@ -71,16 +72,15 @@ export class AvalancheSetDeveloperModeHandler extends DAppRequestHandler { throw new Error('Unrecognized domain'); } - const network = await this.networkService.getNetwork( - isTestmode ? ChainId.AVALANCHE_TESTNET_ID : ChainId.AVALANCHE_MAINNET_ID + await this.networkService.setNetwork( + domain, + chainIdToCaip( + isTestmode + ? ChainId.AVALANCHE_TESTNET_ID + : ChainId.AVALANCHE_MAINNET_ID + ) ); - if (!network) { - throw new Error('Target network not found'); - } - - await this.networkService.setNetwork(domain, network); - onSuccess(null); } catch (e) { onError(e); diff --git a/src/background/services/network/handlers/saveCustomNetwork.ts b/src/background/services/network/handlers/saveCustomNetwork.ts index 492c706e..aeb56499 100644 --- a/src/background/services/network/handlers/saveCustomNetwork.ts +++ b/src/background/services/network/handlers/saveCustomNetwork.ts @@ -1,10 +1,10 @@ import { ExtensionRequest } from '@src/background/connections/extensionConnection/models'; import { ExtensionRequestHandler } from '@src/background/connections/models'; -import { resolve } from '@src/utils/promiseResolver'; import { injectable } from 'tsyringe'; import { NetworkService } from '../NetworkService'; import { CustomNetworkPayload } from '../models'; import { runtime } from 'webextension-polyfill'; +import { resolve } from '@avalabs/core-utils-sdk'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.NETWORK_SAVE_CUSTOM, @@ -43,14 +43,14 @@ export class SaveCustomNetworkHandler implements HandlerType { this.networkService.saveCustomNetwork(network) ); - if (err) { + if (err || !addedNetwork) { return { ...request, - error: err.toString(), + error: err?.toString() ?? 'Adding custom network failed', }; } - await this.networkService.setNetwork(runtime.id, addedNetwork); + await this.networkService.setNetwork(runtime.id, addedNetwork.caipId); return { ...request, diff --git a/src/background/services/network/handlers/setActiveNetwork.ts b/src/background/services/network/handlers/setActiveNetwork.ts index fb4ec53d..a351326c 100644 --- a/src/background/services/network/handlers/setActiveNetwork.ts +++ b/src/background/services/network/handlers/setActiveNetwork.ts @@ -21,19 +21,8 @@ export class SetActiveNetworkHandler implements HandlerType { handle: HandlerType['handle'] = async ({ request }) => { const [scope] = request.params; - const [network, networkErr] = await resolve( - this.networkService.getNetwork(scope) - ); - - if (networkErr || !network) { - return { - ...request, - error: 'Ttarget network not found', - }; - } - const [, err] = await resolve( - this.networkService.setNetwork(runtime.id, network) + this.networkService.setNetwork(runtime.id, scope) ); if (err) { diff --git a/src/background/services/network/handlers/setDeveloperMode.ts b/src/background/services/network/handlers/setDeveloperMode.ts index 5c2f9564..2cbe146b 100644 --- a/src/background/services/network/handlers/setDeveloperMode.ts +++ b/src/background/services/network/handlers/setDeveloperMode.ts @@ -7,6 +7,7 @@ import { ExtensionRequestHandler } from '@src/background/connections/models'; import { resolve } from '@src/utils/promiseResolver'; import { NetworkService } from '../NetworkService'; +import { chainIdToCaip } from '@src/utils/caipConversion'; type HandlerType = ExtensionRequestHandler< ExtensionRequest.NETWORK_SET_DEVELOPER_MODE, @@ -22,19 +23,15 @@ export class SetDevelopermodeNetworkHandler implements HandlerType { handle: HandlerType['handle'] = async ({ request }) => { const [enableDeveloperMode] = request.params; - - const network = await this.networkService.getNetwork( - enableDeveloperMode - ? ChainId.AVALANCHE_TESTNET_ID - : ChainId.AVALANCHE_MAINNET_ID - ); - - if (!network) { - throw new Error('Target network not found'); - } - const [, err] = await resolve( - this.networkService.setNetwork(runtime.id, network) + this.networkService.setNetwork( + runtime.id, + chainIdToCaip( + enableDeveloperMode + ? ChainId.AVALANCHE_TESTNET_ID + : ChainId.AVALANCHE_MAINNET_ID + ) + ) ); if (err) { diff --git a/src/background/services/network/handlers/wallet_addEthereumChain.test.ts b/src/background/services/network/handlers/wallet_addEthereumChain.test.ts index 2832ab46..23cbc659 100644 --- a/src/background/services/network/handlers/wallet_addEthereumChain.test.ts +++ b/src/background/services/network/handlers/wallet_addEthereumChain.test.ts @@ -568,9 +568,7 @@ describe('background/services/network/handlers/wallet_addEthereumChain.ts', () = expect(mockNetworkService.setNetwork).toHaveBeenCalledTimes(1); expect(mockNetworkService.setNetwork).toHaveBeenCalledWith( 'core.app', - expect.objectContaining({ - chainId: 43113, - }) + 'eip155:43113' ); }); @@ -787,7 +785,7 @@ describe('background/services/network/handlers/wallet_addEthereumChain.ts', () = expect(mockNetworkService.setNetwork).toHaveBeenCalledWith( mockPendingAction.site?.domain, - mockPendingAction.displayData.network + mockPendingAction.displayData.network.caipId ); expect(mockNetworkService.saveCustomNetwork).toHaveBeenCalledTimes(1); expect(mockNetworkService.saveCustomNetwork).toHaveBeenCalledWith({ @@ -830,6 +828,7 @@ describe('background/services/network/handlers/wallet_addEthereumChain.ts', () = const network = { ...mockPendingAction.displayData.network, chainId: 43113, + caipId: 'eip155:43113', }; await handler.onActionApproved( @@ -850,7 +849,7 @@ describe('background/services/network/handlers/wallet_addEthereumChain.ts', () = expect(mockNetworkService.saveCustomNetwork).not.toHaveBeenCalled(); expect(mockNetworkService.setNetwork).toHaveBeenCalledWith( mockPendingAction.site?.domain, - network + network.caipId ); expect(successHandler).toHaveBeenCalledTimes(1); @@ -873,6 +872,7 @@ describe('background/services/network/handlers/wallet_addEthereumChain.ts', () = network: { ...mockPendingAction.displayData.network, chainId: 43113, + caipId: 'eip155:43113', }, options: { requiresGlacierApiKey: false, diff --git a/src/background/services/network/handlers/wallet_addEthereumChain.ts b/src/background/services/network/handlers/wallet_addEthereumChain.ts index 0cbb5af4..15fbcec3 100644 --- a/src/background/services/network/handlers/wallet_addEthereumChain.ts +++ b/src/background/services/network/handlers/wallet_addEthereumChain.ts @@ -188,7 +188,7 @@ export class WalletAddEthereumChainHandler extends DAppRequestHandler { await this.networkService.saveCustomNetwork(network); } - await this.networkService.setNetwork(domain, network); + await this.networkService.setNetwork(domain, network.caipId); } onActionApproved = async ( diff --git a/src/background/services/network/handlers/wallet_switchEthereumChain.ts b/src/background/services/network/handlers/wallet_switchEthereumChain.ts index c376ab76..58e79c91 100644 --- a/src/background/services/network/handlers/wallet_switchEthereumChain.ts +++ b/src/background/services/network/handlers/wallet_switchEthereumChain.ts @@ -55,7 +55,7 @@ export class WalletSwitchEthereumChainHandler extends DAppRequestHandler { if (skipApproval) { await this.networkService.setNetwork( request.site.domain, - supportedNetwork + supportedNetwork.caipId ); return { ...request, result: null }; } @@ -98,7 +98,7 @@ export class WalletSwitchEthereumChainHandler extends DAppRequestHandler { try { await this.networkService.setNetwork( pendingAction.site.domain, - pendingAction.displayData.network + pendingAction.displayData.network.caipId ); onSuccess(null); diff --git a/src/background/services/onboarding/finalizeOnboarding.ts b/src/background/services/onboarding/finalizeOnboarding.ts index 0f778972..c4341418 100644 --- a/src/background/services/onboarding/finalizeOnboarding.ts +++ b/src/background/services/onboarding/finalizeOnboarding.ts @@ -26,10 +26,8 @@ export async function finalizeOnboarding({ await networkService.addFavoriteNetwork(ChainId.BITCOIN); await networkService.addFavoriteNetwork(ChainId.ETHEREUM_HOMESTEAD); - await networkService.setNetwork( - runtime.id, - await networkService.getAvalancheNetwork() - ); + const cChain = await networkService.getAvalancheNetwork(); + await networkService.setNetwork(runtime.id, cChain.caipId); const allAccounts = accountsService.getAccounts(); const addedAccounts = allAccounts.primary[walletId];