Skip to content

feat: add Monad Testnet #14963

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

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,71 @@ exports[`NetworksSettings should render correctly 1`] = `
</View>
</TouchableOpacity>
</View>
<View>
<TouchableOpacity
onLongPress={[Function]}
onPress={[Function]}
>
<View
style={
{
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"paddingVertical": 12,
}
}
>
<Image
source={1}
style={
{
"borderRadius": 10,
"height": 20,
"marginRight": 16,
"marginTop": 2,
"width": 20,
}
}
/>
<Text
style={
{
"color": "#121314",
"fontFamily": "CentraNo1-Book",
"fontSize": 16,
"fontWeight": "400",
}
}
>
Monad Testnet
</Text>
<Text
allowFontScaling={false}
selectable={false}
style={
[
{
"color": "#121314",
"fontSize": 20,
},
{
"marginLeft": 8,
},
{
"fontFamily": "FontAwesome",
"fontStyle": "normal",
"fontWeight": "normal",
},
{},
]
}
>
</Text>
</View>
</TouchableOpacity>
</View>
</View>
</RCTScrollView>
<TouchableOpacity
Expand Down
7 changes: 6 additions & 1 deletion app/constants/network.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const LINEA_GOERLI = 'linea-goerli';
export const LINEA_SEPOLIA = 'linea-sepolia';
export const LINEA_MAINNET = 'linea-mainnet';
export const MEGAETH_TESTNET = 'megaeth-testnet';
export const MONAD_TESTNET = 'monad-testnet';

export const RPC = NetworkType.rpc;
export const NO_RPC_BLOCK_EXPLORER = 'NO_BLOCK_EXPLORER';
Expand Down Expand Up @@ -43,6 +44,7 @@ export const NETWORKS_CHAIN_ID = {
BERACHAIN: toHex('80085'),
METACHAIN_ONE: toHex('112358'),
MEGAETH_TESTNET: toHex('6342'),
MONAD_TESTNET: toHex('10143'),
};

// To add a deprecation warning to a network, add it to the array
Expand Down Expand Up @@ -72,6 +74,7 @@ export const CHAINLIST_CURRENCY_SYMBOLS_MAP = {
LINEA_MAINNET: 'ETH',
ZKSYNC_ERA: 'ETH',
MEGAETH_TESTNET: 'MegaETH',
MONAD_TESTNET: 'MON',
};

export const CURRENCY_SYMBOL_BY_CHAIN_ID = {
Expand All @@ -94,12 +97,14 @@ export const CURRENCY_SYMBOL_BY_CHAIN_ID = {
CHAINLIST_CURRENCY_SYMBOLS_MAP.LINEA_MAINNET,
[NETWORKS_CHAIN_ID.ZKSYNC_ERA]: CHAINLIST_CURRENCY_SYMBOLS_MAP.ZKSYNC_ERA,
[NETWORKS_CHAIN_ID.MEGAETH_TESTNET]: CHAINLIST_CURRENCY_SYMBOLS_MAP.MEGAETH_TESTNET,
[NETWORKS_CHAIN_ID.MONAD_TESTNET]: CHAINLIST_CURRENCY_SYMBOLS_MAP.MONAD_TESTNET,
};

export const TEST_NETWORK_IDS = [
NETWORKS_CHAIN_ID.GOERLI,
NETWORKS_CHAIN_ID.SEPOLIA,
NETWORKS_CHAIN_ID.LINEA_GOERLI,
NETWORKS_CHAIN_ID.LINEA_SEPOLIA,
NETWORKS_CHAIN_ID.MEGAETH_TESTNET
NETWORKS_CHAIN_ID.MEGAETH_TESTNET,
NETWORKS_CHAIN_ID.MONAD_TESTNET,
];
2 changes: 1 addition & 1 deletion app/core/Engine/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ export class Engine {
fetch,
btoa,
}),
additionalDefaultNetworks: [ChainId['megaeth-testnet']],
additionalDefaultNetworks: [ChainId['megaeth-testnet'], ChainId['monad-testnet']],
};
const networkController = new NetworkController(networkControllerOpts);

Expand Down
3 changes: 2 additions & 1 deletion app/images/image-icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import BTC from './bitcoin-logo.png';
import BASE from './base.png';
import MEGAETH_TESTNET from './megaeth-testnet-logo.png';
import XRPLEVM_XRP_TOKEN from './xrp-logo.png';

import MONAD_TESTNET from './monad-testnet-logo.png';

export default {
PALM,
Expand All @@ -47,4 +47,5 @@ export default {
BASE,
'MEGAETH-TESTNET': MEGAETH_TESTNET,
XRPLEVM_XRP_TOKEN,
'MONAD-TESTNET': MONAD_TESTNET,
};
Binary file added app/images/monad-testnet-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
248 changes: 248 additions & 0 deletions app/store/migrations/077.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@

import { captureException } from '@sentry/react-native';
import { cloneDeep } from 'lodash';
import { NetworkConfiguration, RpcEndpointType } from '@metamask/network-controller';
import { Hex } from '@metamask/utils';

import { ensureValidState } from './util';
import migrate from './077';

jest.mock('@sentry/react-native', () => ({
captureException: jest.fn(),
}));

jest.mock('./util', () => ({
ensureValidState: jest.fn(),
}));

const mockedCaptureException = jest.mocked(captureException);
const mockedEnsureValidState = jest.mocked(ensureValidState);

const createTestState = () => ({
engine: {
backgroundState: {
NetworkController: {
selectedNetworkClientId: 'mainnet',
networksMetadata: {},
networkConfigurationsByChainId: {
'0x1': {
chainId: '0x1',
rpcEndpoints: [
{
networkClientId: 'mainnet',
url: 'https://mainnet.infura.io/v3/{infuraProjectId}',
type: 'infura',
},
],
defaultRpcEndpointIndex: 0,
blockExplorerUrls: ['https://etherscan.io'],
defaultBlockExplorerUrlIndex: 0,
name: 'Ethereum Mainnet',
nativeCurrency: 'ETH',
},
'0xaa36a7': {
chainId: '0xaa36a7',
rpcEndpoints: [
{
networkClientId: 'sepolia',
url: 'https://sepolia.infura.io/v3/{infuraProjectId}',
type: 'infura',
},
],
defaultRpcEndpointIndex: 0,
blockExplorerUrls: ['https://sepolia.etherscan.io'],
defaultBlockExplorerUrlIndex: 0,
name: 'Sepolia',
nativeCurrency: 'SepoliaETH',
},
'0xe705': {
chainId: '0xe705',
rpcEndpoints: [
{
networkClientId: 'linea-sepolia',
url: 'https://linea-sepolia.infura.io/v3/{infuraProjectId}',
type: 'infura',
},
],
defaultRpcEndpointIndex: 0,
blockExplorerUrls: ['https://sepolia.lineascan.build'],
defaultBlockExplorerUrlIndex: 0,
name: 'Linea Sepolia',
nativeCurrency: 'LineaETH',
},
'0xe708': {
chainId: '0xe708',
rpcEndpoints: [
{
networkClientId: 'linea-mainnet',
url: 'https://linea-mainnet.infura.io/v3/{infuraProjectId}',
type: 'infura',
},
],
defaultRpcEndpointIndex: 0,
blockExplorerUrls: ['https://lineascan.build'],
defaultBlockExplorerUrlIndex: 0,
name: 'Linea Mainnet',
nativeCurrency: 'ETH',
},
'0x18c6': {
chainId: '0x18c6',
rpcEndpoints: [
{
networkClientId: 'megaeth-testnet',
url: 'https://carrot.megaeth.com/rpc',
type: RpcEndpointType.Custom,
failoverUrls: [],
},
],
defaultRpcEndpointIndex: 0,
blockExplorerUrls: ['https://megaexplorer.xyz'],
defaultBlockExplorerUrlIndex: 0,
name: 'Mega Testnet',
nativeCurrency: 'MegaETH',
},
},
},
}
}
});

const createMonadTestnetConfiguration = (): NetworkConfiguration => ({
chainId: '0x279f',
rpcEndpoints: [
{
networkClientId: 'monad-testnet',
url: 'https://testnet-rpc.monad.xyz',
type: RpcEndpointType.Custom,
failoverUrls: [],
},
],
defaultRpcEndpointIndex: 0,
blockExplorerUrls: ['https://testnet.monadexplorer.com'],
defaultBlockExplorerUrlIndex: 0,
name: 'Monad Testnet',
nativeCurrency: 'MON',
});

describe('Migration 77: Add `Monad Testnet`', () => {
beforeEach(() => {
jest.resetAllMocks();
});

it('returns state unchanged if ensureValidState fails', () => {
const state = { some: 'state' };
mockedEnsureValidState.mockReturnValue(false);

const migratedState = migrate(state);

expect(migratedState).toStrictEqual({ some: 'state' });
expect(mockedCaptureException).not.toHaveBeenCalled();
});

it('adds `Monad Testnet` as default network to state', () => {
const monadTestnetConfiguration = createMonadTestnetConfiguration();
const oldState = createTestState();
mockedEnsureValidState.mockReturnValue(true);

const expectedData = {
engine: {
backgroundState: {
NetworkController: {
...oldState.engine.backgroundState.NetworkController,
networkConfigurationsByChainId: {
...oldState.engine.backgroundState.NetworkController.networkConfigurationsByChainId,
[monadTestnetConfiguration.chainId]: monadTestnetConfiguration
},
},
}
}
};

const migratedState = migrate(oldState);

expect(migratedState).toStrictEqual(expectedData);
expect(mockedCaptureException).not.toHaveBeenCalled();
});

it('replaces `Monad Testnet` NetworkConfiguration if there is one', () => {
const monadTestnetConfiguration = createMonadTestnetConfiguration();
const oldState = createTestState();
const networkConfigurationsByChainId = oldState.engine.backgroundState.NetworkController.networkConfigurationsByChainId as Record<Hex, NetworkConfiguration>;
networkConfigurationsByChainId[monadTestnetConfiguration.chainId] = {
...monadTestnetConfiguration,
rpcEndpoints: [
{
networkClientId: 'some-client-id',
url: 'https://some-url.com/rpc',
type: RpcEndpointType.Custom,
},
],
};
mockedEnsureValidState.mockReturnValue(true);

const expectedData = {
engine: {
backgroundState: {
NetworkController: {
...oldState.engine.backgroundState.NetworkController,
networkConfigurationsByChainId: {
...oldState.engine.backgroundState.NetworkController.networkConfigurationsByChainId,
[monadTestnetConfiguration.chainId]: monadTestnetConfiguration
},
},
}
}
};

const migratedState = migrate(oldState);

expect(migratedState).toStrictEqual(expectedData);
expect(mockedCaptureException).not.toHaveBeenCalled();
});

it.each([{
state: {
engine: {}
},
test: 'empty engine state',
}, {
state: {
engine: {
backgroundState: {}
}
},
test: 'empty backgroundState',
}, {
state: {
engine: {
backgroundState: {
NetworkController: 'invalid'
}
},
},
test: 'invalid NetworkController state'
}, {
state: {
engine: {
backgroundState: {
NetworkController: {
networkConfigurationsByChainId: 'invalid'
}
}
},
},
test: 'invalid networkConfigurationsByChainId state'
}
])('does not modify state if the state is invalid - $test', ({ state }) => {
const orgState = cloneDeep(state);
mockedEnsureValidState.mockReturnValue(true);

const migratedState = migrate(state);

// State should be unchanged
expect(migratedState).toStrictEqual(orgState);
expect(mockedCaptureException).toHaveBeenCalledWith(
new Error('Migration 77: NetworkController or networkConfigurationsByChainId not found in state'),
);
});
});
Loading
Loading