Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: treat BTC as known network #37

Merged
merged 3 commits into from
Sep 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 75 additions & 54 deletions src/background/services/network/NetworkService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -115,46 +147,23 @@ 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(() => {
process.env = env;
});

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 () => {
Expand All @@ -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');

Expand All @@ -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');

Expand All @@ -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');

Expand Down Expand Up @@ -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,
Expand All @@ -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',
Expand All @@ -267,25 +276,22 @@ 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);
});

it(`changes dApp's network to the specified chain`, async () => {
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({
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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,
Expand All @@ -340,21 +346,32 @@ 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);
});

it('notifies about the change', async () => {
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,
Expand All @@ -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',
},
Expand Down Expand Up @@ -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',
Expand All @@ -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'
);
});
Expand All @@ -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);
Expand Down
19 changes: 13 additions & 6 deletions src/background/services/network/NetworkService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
),
Expand Down Expand Up @@ -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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
Expand Down
Loading
Loading