Skip to content

Commit b5ed096

Browse files
feat: add Monad Testnet (#14963)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> This PR is to add Monad Testnet as default network It also include a migration script to add/replace the Monad testnet network for existing user ### **Change**: - Bump `controller-utils` from 11.7.0 to 11.8.0 - Bump `network-controller` from 23.2.0 to 23.4.0 - Add Monad Testnet to default network via NetworkController options - `additionalDefaultNetworks` - Add migration script to replace/add Monad Testnet to the NetworkController state (TODO) - Update Unit Test for Monad Testnet ## **Related issues** Fixes: ## **Manual testing steps** 1. Setup a new wallet 2. Click on network menu and show test networks 3. You will see Sepolia , Linea Sepolia , Mega Testnet, Monad Testnet on test networks section 4. Click on Monad Testnet to switch the network 5. Send a transaction 6. Check the transaction result in the explorer ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> <img width="250" alt="image" src="https://github.com/user-attachments/assets/5f4b5bd7-d4f2-4edb-9e03-43b08206276a" /> <img width="250" alt="image" src="https://github.com/user-attachments/assets/d4c8229b-35f1-4c0a-a729-3d2156cc664b" /> <img width="250" alt="image" src="https://github.com/user-attachments/assets/cc999b1f-82d6-4e46-9d44-233a81412257" /> ## **Pre-merge author checklist** - [x] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent e7f4d24 commit b5ed096

File tree

14 files changed

+493
-16
lines changed

14 files changed

+493
-16
lines changed

app/components/Views/Settings/NetworksSettings/__snapshots__/index.test.tsx.snap

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,71 @@ exports[`NetworksSettings should render correctly 1`] = `
903903
</View>
904904
</TouchableOpacity>
905905
</View>
906+
<View>
907+
<TouchableOpacity
908+
onLongPress={[Function]}
909+
onPress={[Function]}
910+
>
911+
<View
912+
style={
913+
{
914+
"alignItems": "center",
915+
"flex": 1,
916+
"flexDirection": "row",
917+
"paddingVertical": 12,
918+
}
919+
}
920+
>
921+
<Image
922+
source={1}
923+
style={
924+
{
925+
"borderRadius": 10,
926+
"height": 20,
927+
"marginRight": 16,
928+
"marginTop": 2,
929+
"width": 20,
930+
}
931+
}
932+
/>
933+
<Text
934+
style={
935+
{
936+
"color": "#121314",
937+
"fontFamily": "CentraNo1-Book",
938+
"fontSize": 16,
939+
"fontWeight": "400",
940+
}
941+
}
942+
>
943+
Monad Testnet
944+
</Text>
945+
<Text
946+
allowFontScaling={false}
947+
selectable={false}
948+
style={
949+
[
950+
{
951+
"color": "#121314",
952+
"fontSize": 20,
953+
},
954+
{
955+
"marginLeft": 8,
956+
},
957+
{
958+
"fontFamily": "FontAwesome",
959+
"fontStyle": "normal",
960+
"fontWeight": "normal",
961+
},
962+
{},
963+
]
964+
}
965+
>
966+
967+
</Text>
968+
</View>
969+
</TouchableOpacity>
970+
</View>
906971
</View>
907972
</RCTScrollView>
908973
<TouchableOpacity

app/constants/network.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const LINEA_GOERLI = 'linea-goerli';
88
export const LINEA_SEPOLIA = 'linea-sepolia';
99
export const LINEA_MAINNET = 'linea-mainnet';
1010
export const MEGAETH_TESTNET = 'megaeth-testnet';
11+
export const MONAD_TESTNET = 'monad-testnet';
1112

1213
export const RPC = NetworkType.rpc;
1314
export const NO_RPC_BLOCK_EXPLORER = 'NO_BLOCK_EXPLORER';
@@ -43,6 +44,7 @@ export const NETWORKS_CHAIN_ID = {
4344
BERACHAIN: toHex('80085'),
4445
METACHAIN_ONE: toHex('112358'),
4546
MEGAETH_TESTNET: toHex('6342'),
47+
MONAD_TESTNET: toHex('10143'),
4648
};
4749

4850
// To add a deprecation warning to a network, add it to the array
@@ -72,6 +74,7 @@ export const CHAINLIST_CURRENCY_SYMBOLS_MAP = {
7274
LINEA_MAINNET: 'ETH',
7375
ZKSYNC_ERA: 'ETH',
7476
MEGAETH_TESTNET: 'MegaETH',
77+
MONAD_TESTNET: 'MON',
7578
};
7679

7780
export const CURRENCY_SYMBOL_BY_CHAIN_ID = {
@@ -94,12 +97,14 @@ export const CURRENCY_SYMBOL_BY_CHAIN_ID = {
9497
CHAINLIST_CURRENCY_SYMBOLS_MAP.LINEA_MAINNET,
9598
[NETWORKS_CHAIN_ID.ZKSYNC_ERA]: CHAINLIST_CURRENCY_SYMBOLS_MAP.ZKSYNC_ERA,
9699
[NETWORKS_CHAIN_ID.MEGAETH_TESTNET]: CHAINLIST_CURRENCY_SYMBOLS_MAP.MEGAETH_TESTNET,
100+
[NETWORKS_CHAIN_ID.MONAD_TESTNET]: CHAINLIST_CURRENCY_SYMBOLS_MAP.MONAD_TESTNET,
97101
};
98102

99103
export const TEST_NETWORK_IDS = [
100104
NETWORKS_CHAIN_ID.GOERLI,
101105
NETWORKS_CHAIN_ID.SEPOLIA,
102106
NETWORKS_CHAIN_ID.LINEA_GOERLI,
103107
NETWORKS_CHAIN_ID.LINEA_SEPOLIA,
104-
NETWORKS_CHAIN_ID.MEGAETH_TESTNET
108+
NETWORKS_CHAIN_ID.MEGAETH_TESTNET,
109+
NETWORKS_CHAIN_ID.MONAD_TESTNET,
105110
];

app/core/Engine/Engine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ export class Engine {
319319
fetch,
320320
btoa,
321321
}),
322-
additionalDefaultNetworks: [ChainId['megaeth-testnet']],
322+
additionalDefaultNetworks: [ChainId['megaeth-testnet'], ChainId['monad-testnet']],
323323
};
324324
const networkController = new NetworkController(networkControllerOpts);
325325

app/images/image-icons.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import BTC from './bitcoin-logo.png';
2020
import BASE from './base.png';
2121
import MEGAETH_TESTNET from './megaeth-testnet-logo.png';
2222
import XRPLEVM_XRP_TOKEN from './xrp-logo.png';
23+
import MONAD_TESTNET from './monad-testnet-logo.png';
2324
import MATCHAIN from './matchain.png';
2425
import FLOW from './flow.png';
2526
import LENS from './lens.png';
@@ -51,6 +52,7 @@ export default {
5152
BASE,
5253
'MEGAETH-TESTNET': MEGAETH_TESTNET,
5354
XRPLEVM_XRP_TOKEN,
55+
'MONAD-TESTNET': MONAD_TESTNET,
5456
MATCHAIN,
5557
FLOW,
5658
LENS,

app/images/monad-testnet-logo.png

5.29 KB
Loading

app/store/migrations/077.test.ts

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
2+
import { captureException } from '@sentry/react-native';
3+
import { cloneDeep } from 'lodash';
4+
import { NetworkConfiguration, RpcEndpointType } from '@metamask/network-controller';
5+
import { Hex } from '@metamask/utils';
6+
7+
import { ensureValidState } from './util';
8+
import migrate from './077';
9+
10+
jest.mock('@sentry/react-native', () => ({
11+
captureException: jest.fn(),
12+
}));
13+
14+
jest.mock('./util', () => ({
15+
ensureValidState: jest.fn(),
16+
}));
17+
18+
const mockedCaptureException = jest.mocked(captureException);
19+
const mockedEnsureValidState = jest.mocked(ensureValidState);
20+
21+
const createTestState = () => ({
22+
engine: {
23+
backgroundState: {
24+
NetworkController: {
25+
selectedNetworkClientId: 'mainnet',
26+
networksMetadata: {},
27+
networkConfigurationsByChainId: {
28+
'0x1': {
29+
chainId: '0x1',
30+
rpcEndpoints: [
31+
{
32+
networkClientId: 'mainnet',
33+
url: 'https://mainnet.infura.io/v3/{infuraProjectId}',
34+
type: 'infura',
35+
},
36+
],
37+
defaultRpcEndpointIndex: 0,
38+
blockExplorerUrls: ['https://etherscan.io'],
39+
defaultBlockExplorerUrlIndex: 0,
40+
name: 'Ethereum Mainnet',
41+
nativeCurrency: 'ETH',
42+
},
43+
'0xaa36a7': {
44+
chainId: '0xaa36a7',
45+
rpcEndpoints: [
46+
{
47+
networkClientId: 'sepolia',
48+
url: 'https://sepolia.infura.io/v3/{infuraProjectId}',
49+
type: 'infura',
50+
},
51+
],
52+
defaultRpcEndpointIndex: 0,
53+
blockExplorerUrls: ['https://sepolia.etherscan.io'],
54+
defaultBlockExplorerUrlIndex: 0,
55+
name: 'Sepolia',
56+
nativeCurrency: 'SepoliaETH',
57+
},
58+
'0xe705': {
59+
chainId: '0xe705',
60+
rpcEndpoints: [
61+
{
62+
networkClientId: 'linea-sepolia',
63+
url: 'https://linea-sepolia.infura.io/v3/{infuraProjectId}',
64+
type: 'infura',
65+
},
66+
],
67+
defaultRpcEndpointIndex: 0,
68+
blockExplorerUrls: ['https://sepolia.lineascan.build'],
69+
defaultBlockExplorerUrlIndex: 0,
70+
name: 'Linea Sepolia',
71+
nativeCurrency: 'LineaETH',
72+
},
73+
'0xe708': {
74+
chainId: '0xe708',
75+
rpcEndpoints: [
76+
{
77+
networkClientId: 'linea-mainnet',
78+
url: 'https://linea-mainnet.infura.io/v3/{infuraProjectId}',
79+
type: 'infura',
80+
},
81+
],
82+
defaultRpcEndpointIndex: 0,
83+
blockExplorerUrls: ['https://lineascan.build'],
84+
defaultBlockExplorerUrlIndex: 0,
85+
name: 'Linea Mainnet',
86+
nativeCurrency: 'ETH',
87+
},
88+
'0x18c6': {
89+
chainId: '0x18c6',
90+
rpcEndpoints: [
91+
{
92+
networkClientId: 'megaeth-testnet',
93+
url: 'https://carrot.megaeth.com/rpc',
94+
type: RpcEndpointType.Custom,
95+
failoverUrls: [],
96+
},
97+
],
98+
defaultRpcEndpointIndex: 0,
99+
blockExplorerUrls: ['https://megaexplorer.xyz'],
100+
defaultBlockExplorerUrlIndex: 0,
101+
name: 'Mega Testnet',
102+
nativeCurrency: 'MegaETH',
103+
},
104+
},
105+
},
106+
}
107+
}
108+
});
109+
110+
const createMonadTestnetConfiguration = (): NetworkConfiguration => ({
111+
chainId: '0x279f',
112+
rpcEndpoints: [
113+
{
114+
networkClientId: 'monad-testnet',
115+
url: 'https://testnet-rpc.monad.xyz',
116+
type: RpcEndpointType.Custom,
117+
failoverUrls: [],
118+
},
119+
],
120+
defaultRpcEndpointIndex: 0,
121+
blockExplorerUrls: ['https://testnet.monadexplorer.com'],
122+
defaultBlockExplorerUrlIndex: 0,
123+
name: 'Monad Testnet',
124+
nativeCurrency: 'MON',
125+
});
126+
127+
describe('Migration 77: Add `Monad Testnet`', () => {
128+
beforeEach(() => {
129+
jest.resetAllMocks();
130+
});
131+
132+
it('returns state unchanged if ensureValidState fails', () => {
133+
const state = { some: 'state' };
134+
mockedEnsureValidState.mockReturnValue(false);
135+
136+
const migratedState = migrate(state);
137+
138+
expect(migratedState).toStrictEqual({ some: 'state' });
139+
expect(mockedCaptureException).not.toHaveBeenCalled();
140+
});
141+
142+
it('adds `Monad Testnet` as default network to state', () => {
143+
const monadTestnetConfiguration = createMonadTestnetConfiguration();
144+
const oldState = createTestState();
145+
mockedEnsureValidState.mockReturnValue(true);
146+
147+
const expectedData = {
148+
engine: {
149+
backgroundState: {
150+
NetworkController: {
151+
...oldState.engine.backgroundState.NetworkController,
152+
networkConfigurationsByChainId: {
153+
...oldState.engine.backgroundState.NetworkController.networkConfigurationsByChainId,
154+
[monadTestnetConfiguration.chainId]: monadTestnetConfiguration
155+
},
156+
},
157+
}
158+
}
159+
};
160+
161+
const migratedState = migrate(oldState);
162+
163+
expect(migratedState).toStrictEqual(expectedData);
164+
expect(mockedCaptureException).not.toHaveBeenCalled();
165+
});
166+
167+
it('replaces `Monad Testnet` NetworkConfiguration if there is one', () => {
168+
const monadTestnetConfiguration = createMonadTestnetConfiguration();
169+
const oldState = createTestState();
170+
const networkConfigurationsByChainId = oldState.engine.backgroundState.NetworkController.networkConfigurationsByChainId as Record<Hex, NetworkConfiguration>;
171+
networkConfigurationsByChainId[monadTestnetConfiguration.chainId] = {
172+
...monadTestnetConfiguration,
173+
rpcEndpoints: [
174+
{
175+
networkClientId: 'some-client-id',
176+
url: 'https://some-url.com/rpc',
177+
type: RpcEndpointType.Custom,
178+
},
179+
],
180+
};
181+
mockedEnsureValidState.mockReturnValue(true);
182+
183+
const expectedData = {
184+
engine: {
185+
backgroundState: {
186+
NetworkController: {
187+
...oldState.engine.backgroundState.NetworkController,
188+
networkConfigurationsByChainId: {
189+
...oldState.engine.backgroundState.NetworkController.networkConfigurationsByChainId,
190+
[monadTestnetConfiguration.chainId]: monadTestnetConfiguration
191+
},
192+
},
193+
}
194+
}
195+
};
196+
197+
const migratedState = migrate(oldState);
198+
199+
expect(migratedState).toStrictEqual(expectedData);
200+
expect(mockedCaptureException).not.toHaveBeenCalled();
201+
});
202+
203+
it.each([{
204+
state: {
205+
engine: {}
206+
},
207+
test: 'empty engine state',
208+
}, {
209+
state: {
210+
engine: {
211+
backgroundState: {}
212+
}
213+
},
214+
test: 'empty backgroundState',
215+
}, {
216+
state: {
217+
engine: {
218+
backgroundState: {
219+
NetworkController: 'invalid'
220+
}
221+
},
222+
},
223+
test: 'invalid NetworkController state'
224+
}, {
225+
state: {
226+
engine: {
227+
backgroundState: {
228+
NetworkController: {
229+
networkConfigurationsByChainId: 'invalid'
230+
}
231+
}
232+
},
233+
},
234+
test: 'invalid networkConfigurationsByChainId state'
235+
}
236+
])('does not modify state if the state is invalid - $test', ({ state }) => {
237+
const orgState = cloneDeep(state);
238+
mockedEnsureValidState.mockReturnValue(true);
239+
240+
const migratedState = migrate(state);
241+
242+
// State should be unchanged
243+
expect(migratedState).toStrictEqual(orgState);
244+
expect(mockedCaptureException).toHaveBeenCalledWith(
245+
new Error('Migration 77: NetworkController or networkConfigurationsByChainId not found in state'),
246+
);
247+
});
248+
});

0 commit comments

Comments
 (0)