mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +01:00
Merge branch 'develop' of github.com:MetaMask/metamask-extension into minimal
This commit is contained in:
commit
bd45b0eca2
@ -263,12 +263,12 @@ const state = {
|
||||
enabled: true,
|
||||
id: 'local:http://localhost:8080/',
|
||||
initialPermissions: {
|
||||
snap_confirm: {},
|
||||
snap_dialog: {},
|
||||
},
|
||||
manifest: {
|
||||
description: 'An example MetaMask Snap.',
|
||||
initialPermissions: {
|
||||
snap_confirm: {},
|
||||
snap_dialog: {},
|
||||
},
|
||||
manifestVersion: '0.1',
|
||||
proposedName: 'MetaMask Example Snap',
|
||||
@ -298,7 +298,7 @@ const state = {
|
||||
enabled: true,
|
||||
id: 'npm:http://localhost:8080/',
|
||||
initialPermissions: {
|
||||
snap_confirm: {},
|
||||
snap_dialog: {},
|
||||
eth_accounts: {},
|
||||
snap_manageState: {},
|
||||
},
|
||||
@ -306,7 +306,7 @@ const state = {
|
||||
description:
|
||||
'This swap provides developers everywhere access to an entirely new data storage paradigm, even letting your programs store data autonomously. Learn more.',
|
||||
initialPermissions: {
|
||||
snap_confirm: {},
|
||||
snap_dialog: {},
|
||||
eth_accounts: {},
|
||||
snap_manageState: {},
|
||||
},
|
||||
@ -1349,9 +1349,9 @@ const state = {
|
||||
},
|
||||
'local:http://localhost:8080/': {
|
||||
permissions: {
|
||||
snap_confirm: {
|
||||
snap_dialog: {
|
||||
invoker: 'local:http://localhost:8080/',
|
||||
parentCapability: 'snap_confirm',
|
||||
parentCapability: 'snap_dialog',
|
||||
id: 'a7342F4b-beae-4525-a36c-c0635fd03359',
|
||||
date: 1620710693178,
|
||||
caveats: [],
|
||||
|
4
app/_locales/de/messages.json
generated
4
app/_locales/de/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Regelmäßige Transaktionen planen und ausführen.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Bestätigung in MetaMask anzeigen.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Dialogfenster in MetaMask anzeigen.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/el/messages.json
generated
4
app/_locales/el/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Προγραμματισμός και εκτέλεση περιοδικών ενεργειών.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Εμφάνιση επιβεβαίωσης στο MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Εμφάνιση παραθύρων διαλόγου στο MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
8
app/_locales/en/messages.json
generated
8
app/_locales/en/messages.json
generated
@ -2914,14 +2914,6 @@
|
||||
"message": "Allow the snap to perform actions that run periodically at fixed times, dates, or intervals. This can be used to trigger time-sensitive interactions or notifications.",
|
||||
"description": "An extended description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Display a confirmation in MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_customConfirmationDescription": {
|
||||
"message": "Allow the snap to display MetaMask popups with custom text, and buttons to approve or reject an action.",
|
||||
"description": "An extended description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Display dialog windows in MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/es/messages.json
generated
4
app/_locales/es/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Programar y ejecutar acciones periódicas.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Mostrar una confirmación en MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Mostrar ventanas de diálogo en MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/fr/messages.json
generated
4
app/_locales/fr/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Planifiez et exécutez des actions périodiques.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Afficher une confirmation dans MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Afficher les boîtes de dialogue dans MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/hi/messages.json
generated
4
app/_locales/hi/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "समय-समय पर आने वाले क्रियाओं को शेड्यूल और निष्पादित करें।",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "MetaMask में पुष्टि को दर्शाएं।",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "MetaMask में डायलॉग विंडो प्रदर्शित करें।",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/id/messages.json
generated
4
app/_locales/id/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Jadwalkan dan lakukan tindakan berkala.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Tampilkan konfirmasi di MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Tampilkan jendela dialog di MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/ja/messages.json
generated
4
app/_locales/ja/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "定期的なアクションのスケジュール設定と実行。",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "MetaMask に確認を表示します。",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "MetaMask にダイアログウィンドウを表示します。",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/ko/messages.json
generated
4
app/_locales/ko/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "정기적 활동 예약 및 실행",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "MetaMask에 확인을 표시합니다.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "MetaMask 대화창 표시",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/pt/messages.json
generated
4
app/_locales/pt/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Agende e execute ações periódicas.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Exibir uma confirmação na MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Exibir janelas de diálogo na MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/ru/messages.json
generated
4
app/_locales/ru/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Планируйте и выполняйте периодические действия.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Показать подтверждение в MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Отображение диалоговых окон в MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/tl/messages.json
generated
4
app/_locales/tl/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Mag-iskedyul at magsagawa ng mga pana-panahong mga aksyon.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Ipakita ang kumpirmasyon sa MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Ipakita ang mga dialog window sa MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/tr/messages.json
generated
4
app/_locales/tr/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Periyodik eylemleri planla ve gerçekleştir.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "MetaMask'te bir onay görüntüle.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "MetaMask'te iletişim kutusu pencerelerini göster.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/vi/messages.json
generated
4
app/_locales/vi/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "Lên lịch và thực hiện các hành động theo định kỳ.",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "Hiển thị xác nhận trong MetaMask.",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "Hiển thị cửa sổ hộp thoại trong MetaMask.",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
4
app/_locales/zh_CN/messages.json
generated
4
app/_locales/zh_CN/messages.json
generated
@ -2697,10 +2697,6 @@
|
||||
"message": "规划并执行定期操作。",
|
||||
"description": "The description for the `snap_cronjob` permission"
|
||||
},
|
||||
"permission_customConfirmation": {
|
||||
"message": "在MetaMask中显示确认。",
|
||||
"description": "The description for the `snap_confirm` permission"
|
||||
},
|
||||
"permission_dialog": {
|
||||
"message": "在 MetaMask 中显示对话框窗口。",
|
||||
"description": "The description for the `snap_dialog` permission"
|
||||
|
@ -278,7 +278,7 @@ describe('DetectTokensController', function () {
|
||||
|
||||
it('should be called on every polling period', async function () {
|
||||
const clock = sandbox.useFakeTimers();
|
||||
network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
await network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
const controller = new DetectTokensController({
|
||||
preferences,
|
||||
network,
|
||||
@ -304,7 +304,7 @@ describe('DetectTokensController', function () {
|
||||
|
||||
it('should not check and add tokens while on unsupported networks', async function () {
|
||||
sandbox.useFakeTimers();
|
||||
network.setProviderType(NETWORK_TYPES.SEPOLIA);
|
||||
await network.setProviderType(NETWORK_TYPES.SEPOLIA);
|
||||
const tokenListMessengerSepolia = new ControllerMessenger().getRestricted({
|
||||
name: 'TokenListController',
|
||||
});
|
||||
@ -337,7 +337,7 @@ describe('DetectTokensController', function () {
|
||||
|
||||
it('should skip adding tokens listed in ignoredTokens array', async function () {
|
||||
sandbox.useFakeTimers();
|
||||
network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
await network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
const controller = new DetectTokensController({
|
||||
preferences,
|
||||
network,
|
||||
@ -388,7 +388,7 @@ describe('DetectTokensController', function () {
|
||||
|
||||
it('should check and add tokens while on supported networks', async function () {
|
||||
sandbox.useFakeTimers();
|
||||
network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
await network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
const controller = new DetectTokensController({
|
||||
preferences,
|
||||
network,
|
||||
@ -483,7 +483,7 @@ describe('DetectTokensController', function () {
|
||||
|
||||
it('should not trigger detect new tokens when not unlocked', async function () {
|
||||
const clock = sandbox.useFakeTimers();
|
||||
network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
await network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
const controller = new DetectTokensController({
|
||||
preferences,
|
||||
network,
|
||||
@ -504,7 +504,7 @@ describe('DetectTokensController', function () {
|
||||
|
||||
it('should not trigger detect new tokens when not open', async function () {
|
||||
const clock = sandbox.useFakeTimers();
|
||||
network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
await network.setProviderType(NETWORK_TYPES.MAINNET);
|
||||
const controller = new DetectTokensController({
|
||||
preferences,
|
||||
network,
|
||||
|
@ -730,7 +730,7 @@ describe('NetworkController', () => {
|
||||
await expect(async () => {
|
||||
await controller.initializeProvider();
|
||||
}).rejects.toThrow(
|
||||
'NetworkController - _configureProvider - unknown type "undefined"',
|
||||
'NetworkController - #configureProvider - unknown type "undefined"',
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -1135,7 +1135,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
expect(oldChainIdResult).toBe('0x1337');
|
||||
|
||||
controller.setProviderType(networkType);
|
||||
await controller.setProviderType(networkType);
|
||||
const promisifiedSendAsync2 = promisify(provider.sendAsync).bind(
|
||||
provider,
|
||||
);
|
||||
@ -2652,22 +2652,10 @@ describe('NetworkController', () => {
|
||||
network1.mockEssentialRpcCalls({
|
||||
eth_getBlockByNumber: {
|
||||
beforeCompleting: async () => {
|
||||
await waitForPublishedEvents({
|
||||
messenger: unrestrictedMessenger,
|
||||
eventType: NetworkControllerEventType.NetworkDidChange,
|
||||
operation: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkStatus'],
|
||||
operation: () => {
|
||||
controller.setProviderType(
|
||||
await controller.setProviderType(
|
||||
anotherNetwork.networkType,
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
const network2 = network1.with({
|
||||
@ -3513,13 +3501,7 @@ describe('NetworkController', () => {
|
||||
{
|
||||
response: SUCCESSFUL_NET_VERSION_RESPONSE,
|
||||
beforeCompleting: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkStatus'],
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -3618,13 +3600,7 @@ describe('NetworkController', () => {
|
||||
result: '111',
|
||||
},
|
||||
beforeCompleting: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkStatus'],
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -3675,13 +3651,7 @@ describe('NetworkController', () => {
|
||||
net_version: {
|
||||
response: SUCCESSFUL_NET_VERSION_RESPONSE,
|
||||
beforeCompleting: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkDetails'],
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -3738,13 +3708,7 @@ describe('NetworkController', () => {
|
||||
network1.mockEssentialRpcCalls({
|
||||
net_version: {
|
||||
beforeCompleting: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkDetails'],
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -3841,13 +3805,7 @@ describe('NetworkController', () => {
|
||||
},
|
||||
},
|
||||
beforeCompleting: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkStatus'],
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
},
|
||||
},
|
||||
],
|
||||
@ -3904,13 +3862,7 @@ describe('NetworkController', () => {
|
||||
network1.mockEssentialRpcCalls({
|
||||
eth_getBlockByNumber: {
|
||||
beforeCompleting: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkStatus'],
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
},
|
||||
},
|
||||
net_version: {
|
||||
@ -3965,13 +3917,7 @@ describe('NetworkController', () => {
|
||||
latestBlock: POST_1559_BLOCK,
|
||||
eth_getBlockByNumber: {
|
||||
beforeCompleting: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkDetails'],
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -4028,13 +3974,7 @@ describe('NetworkController', () => {
|
||||
network1.mockEssentialRpcCalls({
|
||||
eth_getBlockByNumber: {
|
||||
beforeCompleting: async () => {
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkDetails'],
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -4629,7 +4569,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
network.mockEssentialRpcCalls();
|
||||
|
||||
controller.setProviderType(networkType);
|
||||
await controller.setProviderType(networkType);
|
||||
|
||||
expect(controller.store.getState().provider).toStrictEqual({
|
||||
type: networkType,
|
||||
@ -4662,6 +4602,8 @@ describe('NetworkController', () => {
|
||||
messenger: unrestrictedMessenger,
|
||||
eventType: NetworkControllerEventType.NetworkWillChange,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we're capturing an event
|
||||
// emitted partway through the operation
|
||||
controller.setProviderType(networkType);
|
||||
},
|
||||
});
|
||||
@ -4714,6 +4656,8 @@ describe('NetworkController', () => {
|
||||
// happens before networkDidChange
|
||||
count: 1,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we're checking the state
|
||||
// partway through the operation
|
||||
controller.setProviderType(networkType);
|
||||
},
|
||||
});
|
||||
@ -4767,6 +4711,8 @@ describe('NetworkController', () => {
|
||||
// happens before networkDidChange
|
||||
count: 1,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we're checking the state
|
||||
// partway through the operation
|
||||
controller.setProviderType(networkType);
|
||||
},
|
||||
});
|
||||
@ -4786,7 +4732,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
network.mockEssentialRpcCalls();
|
||||
|
||||
controller.setProviderType(networkType);
|
||||
await controller.setProviderType(networkType);
|
||||
|
||||
const { provider } = controller.getProviderAndBlockTracker();
|
||||
assert(provider, 'Provider is somehow unset');
|
||||
@ -4813,7 +4759,7 @@ describe('NetworkController', () => {
|
||||
|
||||
const { provider: providerBefore } =
|
||||
controller.getProviderAndBlockTracker();
|
||||
controller.setProviderType(networkType);
|
||||
await controller.setProviderType(networkType);
|
||||
const { provider: providerAfter } =
|
||||
controller.getProviderAndBlockTracker();
|
||||
|
||||
@ -4836,8 +4782,8 @@ describe('NetworkController', () => {
|
||||
const networkDidChange = await waitForPublishedEvents({
|
||||
messenger: unrestrictedMessenger,
|
||||
eventType: NetworkControllerEventType.NetworkDidChange,
|
||||
operation: () => {
|
||||
controller.setProviderType(networkType);
|
||||
operation: async () => {
|
||||
await controller.setProviderType(networkType);
|
||||
},
|
||||
});
|
||||
|
||||
@ -4872,7 +4818,7 @@ describe('NetworkController', () => {
|
||||
eventType: NetworkControllerEventType.InfuraIsBlocked,
|
||||
});
|
||||
|
||||
controller.setProviderType(networkType);
|
||||
await controller.setProviderType(networkType);
|
||||
|
||||
expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy();
|
||||
expect(await promiseForInfuraIsBlocked).toBeTruthy();
|
||||
@ -4891,13 +4837,7 @@ describe('NetworkController', () => {
|
||||
latestBlock: BLOCK,
|
||||
});
|
||||
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkStatus'],
|
||||
operation: () => {
|
||||
controller.setProviderType(networkType);
|
||||
},
|
||||
});
|
||||
await controller.setProviderType(networkType);
|
||||
|
||||
expect(controller.store.getState().networkStatus).toBe('available');
|
||||
});
|
||||
@ -4921,16 +4861,7 @@ describe('NetworkController', () => {
|
||||
latestBlock: POST_1559_BLOCK,
|
||||
});
|
||||
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkDetails'],
|
||||
// setProviderType clears networkDetails first, and then updates
|
||||
// it to what we expect it to be
|
||||
count: 2,
|
||||
operation: () => {
|
||||
controller.setProviderType(networkType);
|
||||
},
|
||||
});
|
||||
await controller.setProviderType(networkType);
|
||||
|
||||
expect(controller.store.getState().networkDetails).toStrictEqual({
|
||||
EIPS: {
|
||||
@ -4946,7 +4877,7 @@ describe('NetworkController', () => {
|
||||
describe('given a type of "rpc"', () => {
|
||||
it('throws', async () => {
|
||||
await withController(async ({ controller }) => {
|
||||
expect(() => controller.setProviderType('rpc')).toThrow(
|
||||
await expect(() => controller.setProviderType('rpc')).rejects.toThrow(
|
||||
new Error(
|
||||
'NetworkController - cannot call "setProviderType" with type "rpc". Use "setActiveNetwork"',
|
||||
),
|
||||
@ -4958,7 +4889,9 @@ describe('NetworkController', () => {
|
||||
describe('given an invalid Infura network name', () => {
|
||||
it('throws', async () => {
|
||||
await withController(async ({ controller }) => {
|
||||
expect(() => controller.setProviderType('sadlflaksdj')).toThrow(
|
||||
await expect(() =>
|
||||
controller.setProviderType('sadlflaksdj'),
|
||||
).rejects.toThrow(
|
||||
new Error('Unknown Infura provider type "sadlflaksdj".'),
|
||||
);
|
||||
});
|
||||
@ -4992,6 +4925,8 @@ describe('NetworkController', () => {
|
||||
messenger: unrestrictedMessenger,
|
||||
eventType: NetworkControllerEventType.NetworkWillChange,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we want to capture an
|
||||
// event emitted partway throught this operation
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
@ -5032,6 +4967,8 @@ describe('NetworkController', () => {
|
||||
// happens before networkDidChange
|
||||
count: 1,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we want to capture a
|
||||
// state change made partway through the operation
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
@ -5074,6 +5011,8 @@ describe('NetworkController', () => {
|
||||
// happens before networkDidChange
|
||||
count: 1,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we want to check state
|
||||
// partway through the operation
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
@ -5101,7 +5040,7 @@ describe('NetworkController', () => {
|
||||
async ({ controller, network }) => {
|
||||
network.mockEssentialRpcCalls();
|
||||
|
||||
controller.resetConnection();
|
||||
await controller.resetConnection();
|
||||
|
||||
const { provider } = controller.getProviderAndBlockTracker();
|
||||
assert(provider, 'Provider is somehow unset');
|
||||
@ -5140,7 +5079,7 @@ describe('NetworkController', () => {
|
||||
|
||||
const { provider: providerBefore } =
|
||||
controller.getProviderAndBlockTracker();
|
||||
controller.resetConnection();
|
||||
await controller.resetConnection();
|
||||
const { provider: providerAfter } =
|
||||
controller.getProviderAndBlockTracker();
|
||||
|
||||
@ -5171,8 +5110,8 @@ describe('NetworkController', () => {
|
||||
const networkDidChange = await waitForPublishedEvents({
|
||||
messenger: unrestrictedMessenger,
|
||||
eventType: NetworkControllerEventType.NetworkDidChange,
|
||||
operation: () => {
|
||||
controller.resetConnection();
|
||||
operation: async () => {
|
||||
await controller.resetConnection();
|
||||
},
|
||||
});
|
||||
|
||||
@ -5214,7 +5153,7 @@ describe('NetworkController', () => {
|
||||
eventType: NetworkControllerEventType.InfuraIsBlocked,
|
||||
});
|
||||
|
||||
controller.resetConnection();
|
||||
await controller.resetConnection();
|
||||
|
||||
expect(await promiseForNoInfuraIsUnblockedEvents).toBeTruthy();
|
||||
expect(await promiseForInfuraIsBlocked).toBeTruthy();
|
||||
@ -5237,13 +5176,7 @@ describe('NetworkController', () => {
|
||||
async ({ controller, network }) => {
|
||||
network.mockEssentialRpcCalls();
|
||||
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkStatus'],
|
||||
operation: () => {
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
await controller.resetConnection();
|
||||
|
||||
expect(controller.store.getState().networkStatus).toBe(
|
||||
'available',
|
||||
@ -5275,13 +5208,7 @@ describe('NetworkController', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkDetails'],
|
||||
operation: () => {
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
await controller.resetConnection();
|
||||
|
||||
expect(controller.store.getState().networkDetails).toStrictEqual({
|
||||
EIPS: {
|
||||
@ -5326,6 +5253,8 @@ describe('NetworkController', () => {
|
||||
messenger: unrestrictedMessenger,
|
||||
eventType: NetworkControllerEventType.NetworkWillChange,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we're capturing an event
|
||||
// emitted partway through the operation
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
@ -5376,6 +5305,8 @@ describe('NetworkController', () => {
|
||||
// before networkDidChange
|
||||
count: 1,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we want to check state
|
||||
// partway through the operation
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
@ -5426,6 +5357,8 @@ describe('NetworkController', () => {
|
||||
// before networkDidChange
|
||||
count: 1,
|
||||
operation: () => {
|
||||
// Intentionally not awaited because we want to check state
|
||||
// partway through the operation
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
@ -5461,7 +5394,7 @@ describe('NetworkController', () => {
|
||||
async ({ controller, network }) => {
|
||||
network.mockEssentialRpcCalls();
|
||||
|
||||
controller.resetConnection();
|
||||
await controller.resetConnection();
|
||||
|
||||
const { provider } = controller.getProviderAndBlockTracker();
|
||||
assert(provider, 'Provider is somehow unset');
|
||||
@ -5508,12 +5441,7 @@ describe('NetworkController', () => {
|
||||
|
||||
const { provider: providerBefore } =
|
||||
controller.getProviderAndBlockTracker();
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
await controller.resetConnection();
|
||||
const { provider: providerAfter } =
|
||||
controller.getProviderAndBlockTracker();
|
||||
|
||||
@ -5552,8 +5480,8 @@ describe('NetworkController', () => {
|
||||
const networkDidChange = await waitForPublishedEvents({
|
||||
messenger: unrestrictedMessenger,
|
||||
eventType: NetworkControllerEventType.NetworkDidChange,
|
||||
operation: () => {
|
||||
controller.resetConnection();
|
||||
operation: async () => {
|
||||
await controller.resetConnection();
|
||||
},
|
||||
});
|
||||
|
||||
@ -5592,8 +5520,8 @@ describe('NetworkController', () => {
|
||||
const infuraIsUnblocked = await waitForPublishedEvents({
|
||||
messenger: unrestrictedMessenger,
|
||||
eventType: NetworkControllerEventType.InfuraIsUnblocked,
|
||||
operation: () => {
|
||||
controller.resetConnection();
|
||||
operation: async () => {
|
||||
await controller.resetConnection();
|
||||
},
|
||||
});
|
||||
|
||||
@ -5630,13 +5558,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
expect(controller.store.getState().networkStatus).toBe('unknown');
|
||||
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkStatus'],
|
||||
operation: () => {
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
await controller.resetConnection();
|
||||
|
||||
expect(controller.store.getState().networkStatus).toBe('available');
|
||||
},
|
||||
@ -5674,13 +5596,7 @@ describe('NetworkController', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await waitForStateChanges({
|
||||
controller,
|
||||
propertyPath: ['networkDetails'],
|
||||
operation: () => {
|
||||
controller.resetConnection();
|
||||
},
|
||||
});
|
||||
await controller.resetConnection();
|
||||
|
||||
expect(controller.store.getState().networkDetails).toStrictEqual({
|
||||
EIPS: {
|
||||
@ -6393,12 +6309,7 @@ describe('NetworkController', () => {
|
||||
currentNetwork.mockEssentialRpcCalls();
|
||||
previousNetwork.mockEssentialRpcCalls();
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
expect(controller.store.getState().provider).toStrictEqual({
|
||||
type: 'goerli',
|
||||
rpcUrl: '',
|
||||
@ -6466,12 +6377,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
currentNetwork.mockEssentialRpcCalls();
|
||||
previousNetwork.mockEssentialRpcCalls();
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
@ -6518,12 +6424,7 @@ describe('NetworkController', () => {
|
||||
currentNetwork.mockEssentialRpcCalls();
|
||||
previousNetwork.mockEssentialRpcCalls();
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
expect(controller.store.getState().networkStatus).toBe('available');
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
@ -6579,12 +6480,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
previousNetwork.mockEssentialRpcCalls();
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
expect(controller.store.getState().networkDetails).toStrictEqual({
|
||||
EIPS: {
|
||||
1559: true,
|
||||
@ -6645,12 +6541,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
currentNetwork.mockEssentialRpcCalls();
|
||||
previousNetwork.mockEssentialRpcCalls();
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
@ -6700,12 +6591,7 @@ describe('NetworkController', () => {
|
||||
});
|
||||
currentNetwork.mockEssentialRpcCalls();
|
||||
previousNetwork.mockEssentialRpcCalls();
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
|
||||
const { provider: providerBefore } =
|
||||
controller.getProviderAndBlockTracker();
|
||||
@ -6754,12 +6640,7 @@ describe('NetworkController', () => {
|
||||
currentNetwork.mockEssentialRpcCalls();
|
||||
previousNetwork.mockEssentialRpcCalls();
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
@ -6809,12 +6690,7 @@ describe('NetworkController', () => {
|
||||
currentNetwork.mockEssentialRpcCalls();
|
||||
previousNetwork.mockEssentialRpcCalls();
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
@ -6869,12 +6745,7 @@ describe('NetworkController', () => {
|
||||
},
|
||||
});
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
expect(controller.store.getState().networkStatus).toBe('available');
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
@ -6919,12 +6790,7 @@ describe('NetworkController', () => {
|
||||
latestBlock: POST_1559_BLOCK,
|
||||
});
|
||||
|
||||
await waitForLookupNetworkToComplete({
|
||||
controller,
|
||||
operation: () => {
|
||||
controller.setProviderType('goerli');
|
||||
},
|
||||
});
|
||||
await controller.setProviderType('goerli');
|
||||
expect(controller.store.getState().networkDetails).toStrictEqual({
|
||||
EIPS: {
|
||||
1559: false,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import EventEmitter from 'events';
|
||||
import { ComposedStore, ObservableStore } from '@metamask/obs-store';
|
||||
import { ObservableStore } from '@metamask/obs-store';
|
||||
import log from 'loglevel';
|
||||
import {
|
||||
createSwappableProxy,
|
||||
@ -379,6 +379,21 @@ function buildDefaultNetworkConfigurationsState(): NetworkConfigurations {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the default state for the network controller.
|
||||
*
|
||||
* @returns The default network controller state.
|
||||
*/
|
||||
function buildDefaultState() {
|
||||
return {
|
||||
provider: buildDefaultProviderConfigState(),
|
||||
networkId: buildDefaultNetworkIdState(),
|
||||
networkStatus: buildDefaultNetworkStatusState(),
|
||||
networkDetails: buildDefaultNetworkDetailsState(),
|
||||
networkConfigurations: buildDefaultNetworkConfigurationsState(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given argument is a type that our Infura middleware
|
||||
* recognizes. We can't calculate this inline because the usual type of `type`,
|
||||
@ -412,12 +427,7 @@ export class NetworkController extends EventEmitter {
|
||||
/**
|
||||
* The messenger that NetworkController uses to publish events.
|
||||
*/
|
||||
messenger: NetworkControllerMessenger;
|
||||
|
||||
/**
|
||||
* Observable store containing the provider configuration.
|
||||
*/
|
||||
providerStore: ObservableStore<ProviderConfiguration>;
|
||||
#messenger: NetworkControllerMessenger;
|
||||
|
||||
/**
|
||||
* Observable store containing the provider configuration for the previously
|
||||
@ -425,44 +435,23 @@ export class NetworkController extends EventEmitter {
|
||||
*/
|
||||
#previousProviderConfig: ProviderConfiguration;
|
||||
|
||||
/**
|
||||
* Observable store containing the network ID for the current network or null
|
||||
* if there is no current network.
|
||||
*/
|
||||
networkIdStore: ObservableStore<NetworkIdState>;
|
||||
|
||||
/**
|
||||
* Observable store for the network status.
|
||||
*/
|
||||
networkStatusStore: ObservableStore<NetworkStatus>;
|
||||
|
||||
/**
|
||||
* Observable store for details about the network.
|
||||
*/
|
||||
networkDetails: ObservableStore<NetworkDetails>;
|
||||
|
||||
/**
|
||||
* Observable store for network configurations.
|
||||
*/
|
||||
networkConfigurationsStore: ObservableStore<NetworkConfigurations>;
|
||||
|
||||
/**
|
||||
* Observable store containing a combination of data from all of the
|
||||
* individual stores.
|
||||
*/
|
||||
store: ComposedStore<NetworkControllerState>;
|
||||
store: ObservableStore<NetworkControllerState>;
|
||||
|
||||
_provider: SafeEventEmitterProvider | null;
|
||||
#provider: SafeEventEmitterProvider | null;
|
||||
|
||||
_blockTracker: PollingBlockTracker | null;
|
||||
#blockTracker: PollingBlockTracker | null;
|
||||
|
||||
_providerProxy: SwappableProxy<SafeEventEmitterProvider> | null;
|
||||
#providerProxy: SwappableProxy<SafeEventEmitterProvider> | null;
|
||||
|
||||
_blockTrackerProxy: SwappableProxy<PollingBlockTracker> | null;
|
||||
#blockTrackerProxy: SwappableProxy<PollingBlockTracker> | null;
|
||||
|
||||
_infuraProjectId: NetworkControllerOptions['infuraProjectId'];
|
||||
#infuraProjectId: NetworkControllerOptions['infuraProjectId'];
|
||||
|
||||
_trackMetaMetricsEvent: NetworkControllerOptions['trackMetaMetricsEvent'];
|
||||
#trackMetaMetricsEvent: NetworkControllerOptions['trackMetaMetricsEvent'];
|
||||
|
||||
/**
|
||||
* Constructs a network controller.
|
||||
@ -482,51 +471,27 @@ export class NetworkController extends EventEmitter {
|
||||
}: NetworkControllerOptions) {
|
||||
super();
|
||||
|
||||
this.messenger = messenger;
|
||||
this.#messenger = messenger;
|
||||
|
||||
// create stores
|
||||
this.providerStore = new ObservableStore(
|
||||
state.provider || buildDefaultProviderConfigState(),
|
||||
);
|
||||
this.#previousProviderConfig = this.providerStore.getState();
|
||||
this.networkIdStore = new ObservableStore(buildDefaultNetworkIdState());
|
||||
this.networkStatusStore = new ObservableStore(
|
||||
buildDefaultNetworkStatusState(),
|
||||
);
|
||||
// We need to keep track of a few details about the current network.
|
||||
// Ideally we'd merge this.networkStatusStore with this new store, but doing
|
||||
// so will require a decent sized refactor of how we're accessing network
|
||||
// state. Currently this is only used for detecting EIP-1559 support but can
|
||||
// be extended to track other network details.
|
||||
this.networkDetails = new ObservableStore(
|
||||
state.networkDetails || buildDefaultNetworkDetailsState(),
|
||||
);
|
||||
|
||||
this.networkConfigurationsStore = new ObservableStore(
|
||||
state.networkConfigurations || buildDefaultNetworkConfigurationsState(),
|
||||
);
|
||||
|
||||
this.store = new ComposedStore<NetworkControllerState>({
|
||||
provider: this.providerStore,
|
||||
networkId: this.networkIdStore,
|
||||
networkStatus: this.networkStatusStore,
|
||||
networkDetails: this.networkDetails,
|
||||
networkConfigurations: this.networkConfigurationsStore,
|
||||
this.store = new ObservableStore({
|
||||
...buildDefaultState(),
|
||||
...state,
|
||||
});
|
||||
this.#previousProviderConfig = this.store.getState().provider;
|
||||
|
||||
// provider and block tracker
|
||||
this._provider = null;
|
||||
this._blockTracker = null;
|
||||
this.#provider = null;
|
||||
this.#blockTracker = null;
|
||||
|
||||
// provider and block tracker proxies - because the network changes
|
||||
this._providerProxy = null;
|
||||
this._blockTrackerProxy = null;
|
||||
this.#providerProxy = null;
|
||||
this.#blockTrackerProxy = null;
|
||||
|
||||
if (!infuraProjectId || typeof infuraProjectId !== 'string') {
|
||||
throw new Error('Invalid Infura project ID');
|
||||
}
|
||||
this._infuraProjectId = infuraProjectId;
|
||||
this._trackMetaMetricsEvent = trackMetaMetricsEvent;
|
||||
this.#infuraProjectId = infuraProjectId;
|
||||
this.#trackMetaMetricsEvent = trackMetaMetricsEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -535,7 +500,7 @@ export class NetworkController extends EventEmitter {
|
||||
* In-progress requests will not be aborted.
|
||||
*/
|
||||
async destroy(): Promise<void> {
|
||||
await this._blockTracker?.destroy();
|
||||
await this.#blockTracker?.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -543,8 +508,8 @@ export class NetworkController extends EventEmitter {
|
||||
* using the provider to gather details about the network.
|
||||
*/
|
||||
async initializeProvider(): Promise<void> {
|
||||
const { type, rpcUrl, chainId } = this.providerStore.getState();
|
||||
this._configureProvider({ type, rpcUrl, chainId });
|
||||
const { type, rpcUrl, chainId } = this.store.getState().provider;
|
||||
this.#configureProvider({ type, rpcUrl, chainId });
|
||||
await this.lookupNetwork();
|
||||
}
|
||||
|
||||
@ -555,8 +520,8 @@ export class NetworkController extends EventEmitter {
|
||||
provider: SwappableProxy<SafeEventEmitterProvider> | null;
|
||||
blockTracker: SwappableProxy<PollingBlockTracker> | null;
|
||||
} {
|
||||
const provider = this._providerProxy;
|
||||
const blockTracker = this._blockTrackerProxy;
|
||||
const provider = this.#providerProxy;
|
||||
const blockTracker = this.#blockTrackerProxy;
|
||||
return { provider, blockTracker };
|
||||
}
|
||||
|
||||
@ -569,7 +534,7 @@ export class NetworkController extends EventEmitter {
|
||||
* and false otherwise.
|
||||
*/
|
||||
async getEIP1559Compatibility(): Promise<boolean> {
|
||||
const { EIPS } = this.networkDetails.getState();
|
||||
const { EIPS } = this.store.getState().networkDetails;
|
||||
// NOTE: This isn't necessary anymore because the block cache middleware
|
||||
// already prevents duplicate requests from taking place
|
||||
if (EIPS[1559] !== undefined) {
|
||||
@ -584,12 +549,16 @@ export class NetworkController extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
|
||||
const supportsEIP1559 = await this._determineEIP1559Compatibility(provider);
|
||||
this.networkDetails.updateState({
|
||||
const supportsEIP1559 = await this.#determineEIP1559Compatibility(provider);
|
||||
const { networkDetails } = this.store.getState();
|
||||
this.store.updateState({
|
||||
networkDetails: {
|
||||
...networkDetails,
|
||||
EIPS: {
|
||||
...this.networkDetails.getState().EIPS,
|
||||
...networkDetails.EIPS,
|
||||
1559: supportsEIP1559,
|
||||
},
|
||||
},
|
||||
});
|
||||
return supportsEIP1559;
|
||||
}
|
||||
@ -606,7 +575,7 @@ export class NetworkController extends EventEmitter {
|
||||
* blocking requests, or if the network is not Infura-supported.
|
||||
*/
|
||||
async lookupNetwork(): Promise<void> {
|
||||
const { chainId, type } = this.providerStore.getState();
|
||||
const { chainId, type } = this.store.getState().provider;
|
||||
const { provider } = this.getProviderAndBlockTracker();
|
||||
let networkChanged = false;
|
||||
let networkId: NetworkIdState = null;
|
||||
@ -624,9 +593,9 @@ export class NetworkController extends EventEmitter {
|
||||
log.warn(
|
||||
'NetworkController - lookupNetwork aborted due to missing chainId',
|
||||
);
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkStatus();
|
||||
this._resetNetworkDetails();
|
||||
this.#resetNetworkId();
|
||||
this.#resetNetworkStatus();
|
||||
this.#resetNetworkDetails();
|
||||
return;
|
||||
}
|
||||
|
||||
@ -634,20 +603,20 @@ export class NetworkController extends EventEmitter {
|
||||
|
||||
const listener = () => {
|
||||
networkChanged = true;
|
||||
this.messenger.unsubscribe(
|
||||
this.#messenger.unsubscribe(
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
listener,
|
||||
);
|
||||
};
|
||||
this.messenger.subscribe(
|
||||
this.#messenger.subscribe(
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
listener,
|
||||
);
|
||||
|
||||
try {
|
||||
const results = await Promise.all([
|
||||
this._getNetworkId(provider),
|
||||
this._determineEIP1559Compatibility(provider),
|
||||
this.#getNetworkId(provider),
|
||||
this.#determineEIP1559Compatibility(provider),
|
||||
]);
|
||||
const possibleNetworkId = results[0];
|
||||
assertNetworkId(possibleNetworkId);
|
||||
@ -687,37 +656,43 @@ export class NetworkController extends EventEmitter {
|
||||
// in the process of being called, so we don't need to go further.
|
||||
return;
|
||||
}
|
||||
this.messenger.unsubscribe(
|
||||
this.#messenger.unsubscribe(
|
||||
NetworkControllerEventType.NetworkDidChange,
|
||||
listener,
|
||||
);
|
||||
|
||||
this.networkStatusStore.putState(networkStatus);
|
||||
this.store.updateState({
|
||||
networkStatus,
|
||||
});
|
||||
|
||||
if (networkStatus === NetworkStatus.Available) {
|
||||
this.networkIdStore.putState(networkId);
|
||||
this.networkDetails.updateState({
|
||||
const { networkDetails } = this.store.getState();
|
||||
this.store.updateState({
|
||||
networkId,
|
||||
networkDetails: {
|
||||
...networkDetails,
|
||||
EIPS: {
|
||||
...this.networkDetails.getState().EIPS,
|
||||
...networkDetails.EIPS,
|
||||
1559: supportsEIP1559,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkDetails();
|
||||
this.#resetNetworkId();
|
||||
this.#resetNetworkDetails();
|
||||
}
|
||||
|
||||
if (isInfura) {
|
||||
if (networkStatus === NetworkStatus.Available) {
|
||||
this.messenger.publish(NetworkControllerEventType.InfuraIsUnblocked);
|
||||
this.#messenger.publish(NetworkControllerEventType.InfuraIsUnblocked);
|
||||
} else if (networkStatus === NetworkStatus.Blocked) {
|
||||
this.messenger.publish(NetworkControllerEventType.InfuraIsBlocked);
|
||||
this.#messenger.publish(NetworkControllerEventType.InfuraIsBlocked);
|
||||
}
|
||||
} else {
|
||||
// Always publish infuraIsUnblocked regardless of network status to
|
||||
// prevent consumers from being stuck in a blocked state if they were
|
||||
// previously connected to an Infura network that was blocked
|
||||
this.messenger.publish(NetworkControllerEventType.InfuraIsUnblocked);
|
||||
this.#messenger.publish(NetworkControllerEventType.InfuraIsUnblocked);
|
||||
}
|
||||
}
|
||||
|
||||
@ -731,7 +706,7 @@ export class NetworkController extends EventEmitter {
|
||||
*/
|
||||
setActiveNetwork(networkConfigurationId: NetworkConfigurationId): string {
|
||||
const targetNetwork =
|
||||
this.networkConfigurationsStore.getState()[networkConfigurationId];
|
||||
this.store.getState().networkConfigurations[networkConfigurationId];
|
||||
|
||||
if (!targetNetwork) {
|
||||
throw new Error(
|
||||
@ -739,7 +714,7 @@ export class NetworkController extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
this._setProviderConfig({
|
||||
this.#setProviderConfig({
|
||||
type: NETWORK_TYPES.RPC,
|
||||
...targetNetwork,
|
||||
});
|
||||
@ -754,7 +729,7 @@ export class NetworkController extends EventEmitter {
|
||||
* @throws if the `type` is "rpc" or if it is not a known Infura-supported
|
||||
* network.
|
||||
*/
|
||||
setProviderType(type: string): void {
|
||||
async setProviderType(type: string) {
|
||||
assert.notStrictEqual(
|
||||
type,
|
||||
NETWORK_TYPES.RPC,
|
||||
@ -765,7 +740,7 @@ export class NetworkController extends EventEmitter {
|
||||
`Unknown Infura provider type "${type}".`,
|
||||
);
|
||||
const network = BUILT_IN_INFURA_NETWORKS[type];
|
||||
this._setProviderConfig({
|
||||
await this.#setProviderConfig({
|
||||
type,
|
||||
rpcUrl: '',
|
||||
chainId: network.chainId,
|
||||
@ -778,8 +753,8 @@ export class NetworkController extends EventEmitter {
|
||||
/**
|
||||
* Re-initializes the provider and block tracker for the current network.
|
||||
*/
|
||||
resetConnection(): void {
|
||||
this._setProviderConfig(this.providerStore.getState());
|
||||
async resetConnection() {
|
||||
await this.#setProviderConfig(this.store.getState().provider);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -789,8 +764,10 @@ export class NetworkController extends EventEmitter {
|
||||
*/
|
||||
async rollbackToPreviousProvider() {
|
||||
const config = this.#previousProviderConfig;
|
||||
this.providerStore.putState(config);
|
||||
await this._switchNetwork(config);
|
||||
this.store.updateState({
|
||||
provider: config,
|
||||
});
|
||||
await this.#switchNetwork(config);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -800,7 +777,7 @@ export class NetworkController extends EventEmitter {
|
||||
* @returns A promise that either resolves to the block header or null if
|
||||
* there is no latest block, or rejects with an error.
|
||||
*/
|
||||
_getLatestBlock(provider: SafeEventEmitterProvider): Promise<Block | null> {
|
||||
#getLatestBlock(provider: SafeEventEmitterProvider): Promise<Block | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ethQuery = new EthQuery(provider);
|
||||
ethQuery.sendAsync<['latest', false], Block | null>(
|
||||
@ -823,7 +800,7 @@ export class NetworkController extends EventEmitter {
|
||||
* @returns A promise that either resolves to the network ID, or rejects with
|
||||
* an error.
|
||||
*/
|
||||
async _getNetworkId(provider: SafeEventEmitterProvider): Promise<string> {
|
||||
async #getNetworkId(provider: SafeEventEmitterProvider): Promise<string> {
|
||||
const ethQuery = new EthQuery(provider);
|
||||
return await new Promise((resolve, reject) => {
|
||||
ethQuery.sendAsync<never[], string>(
|
||||
@ -842,22 +819,28 @@ export class NetworkController extends EventEmitter {
|
||||
/**
|
||||
* Clears the stored network ID.
|
||||
*/
|
||||
_resetNetworkId(): void {
|
||||
this.networkIdStore.putState(buildDefaultNetworkIdState());
|
||||
#resetNetworkId(): void {
|
||||
this.store.updateState({
|
||||
networkId: buildDefaultNetworkIdState(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets network status to the default ("unknown").
|
||||
*/
|
||||
_resetNetworkStatus(): void {
|
||||
this.networkStatusStore.putState(buildDefaultNetworkStatusState());
|
||||
#resetNetworkStatus(): void {
|
||||
this.store.updateState({
|
||||
networkStatus: buildDefaultNetworkStatusState(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears details previously stored for the network.
|
||||
*/
|
||||
_resetNetworkDetails(): void {
|
||||
this.networkDetails.putState(buildDefaultNetworkDetailsState());
|
||||
#resetNetworkDetails(): void {
|
||||
this.store.updateState({
|
||||
networkDetails: buildDefaultNetworkDetailsState(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -866,10 +849,10 @@ export class NetworkController extends EventEmitter {
|
||||
*
|
||||
* @param providerConfig - The provider configuration.
|
||||
*/
|
||||
async _setProviderConfig(providerConfig: ProviderConfiguration) {
|
||||
this.#previousProviderConfig = this.providerStore.getState();
|
||||
this.providerStore.putState(providerConfig);
|
||||
await this._switchNetwork(providerConfig);
|
||||
async #setProviderConfig(providerConfig: ProviderConfiguration) {
|
||||
this.#previousProviderConfig = this.store.getState().provider;
|
||||
this.store.updateState({ provider: providerConfig });
|
||||
await this.#switchNetwork(providerConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -881,10 +864,10 @@ export class NetworkController extends EventEmitter {
|
||||
* @returns A promise that resolves to true if the network supports EIP-1559
|
||||
* and false otherwise.
|
||||
*/
|
||||
async _determineEIP1559Compatibility(
|
||||
async #determineEIP1559Compatibility(
|
||||
provider: SafeEventEmitterProvider,
|
||||
): Promise<boolean> {
|
||||
const latestBlock = await this._getLatestBlock(provider);
|
||||
const latestBlock = await this.#getLatestBlock(provider);
|
||||
return latestBlock?.baseFeePerGas !== undefined;
|
||||
}
|
||||
|
||||
@ -900,13 +883,13 @@ export class NetworkController extends EventEmitter {
|
||||
* @param providerConfig - The provider configuration object that specifies
|
||||
* the new network.
|
||||
*/
|
||||
async _switchNetwork(providerConfig: ProviderConfiguration) {
|
||||
this.messenger.publish(NetworkControllerEventType.NetworkWillChange);
|
||||
this._resetNetworkId();
|
||||
this._resetNetworkStatus();
|
||||
this._resetNetworkDetails();
|
||||
this._configureProvider(providerConfig);
|
||||
this.messenger.publish(NetworkControllerEventType.NetworkDidChange);
|
||||
async #switchNetwork(providerConfig: ProviderConfiguration) {
|
||||
this.#messenger.publish(NetworkControllerEventType.NetworkWillChange);
|
||||
this.#resetNetworkId();
|
||||
this.#resetNetworkStatus();
|
||||
this.#resetNetworkDetails();
|
||||
this.#configureProvider(providerConfig);
|
||||
this.#messenger.publish(NetworkControllerEventType.NetworkDidChange);
|
||||
await this.lookupNetwork();
|
||||
}
|
||||
|
||||
@ -924,20 +907,20 @@ export class NetworkController extends EventEmitter {
|
||||
* any Infura-supported network).
|
||||
* @throws if the `type` if not a known Infura-supported network.
|
||||
*/
|
||||
_configureProvider({ type, rpcUrl, chainId }: ProviderConfiguration): void {
|
||||
#configureProvider({ type, rpcUrl, chainId }: ProviderConfiguration): void {
|
||||
const isInfura = isInfuraProviderType(type);
|
||||
if (isInfura) {
|
||||
// infura type-based endpoints
|
||||
this._configureInfuraProvider({
|
||||
this.#configureInfuraProvider({
|
||||
type,
|
||||
infuraProjectId: this._infuraProjectId,
|
||||
infuraProjectId: this.#infuraProjectId,
|
||||
});
|
||||
} else if (type === NETWORK_TYPES.RPC && rpcUrl) {
|
||||
// url-based rpc endpoints
|
||||
this._configureStandardProvider(rpcUrl, chainId);
|
||||
this.#configureStandardProvider(rpcUrl, chainId);
|
||||
} else {
|
||||
throw new Error(
|
||||
`NetworkController - _configureProvider - unknown type "${type}"`,
|
||||
`NetworkController - #configureProvider - unknown type "${type}"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -952,20 +935,20 @@ export class NetworkController extends EventEmitter {
|
||||
* @param args.infuraProjectId - An Infura API key. ("Project ID" is a
|
||||
* now-obsolete term we've retained for backward compatibility.)
|
||||
*/
|
||||
_configureInfuraProvider({
|
||||
#configureInfuraProvider({
|
||||
type,
|
||||
infuraProjectId,
|
||||
}: {
|
||||
type: BuiltInInfuraNetwork;
|
||||
infuraProjectId: NetworkControllerOptions['infuraProjectId'];
|
||||
}): void {
|
||||
log.info('NetworkController - configureInfuraProvider', type);
|
||||
log.info('NetworkController - #configureInfuraProvider', type);
|
||||
const { provider, blockTracker } = createNetworkClient({
|
||||
network: type,
|
||||
infuraProjectId,
|
||||
type: NetworkClientType.Infura,
|
||||
});
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker });
|
||||
this.#setProviderAndBlockTracker({ provider, blockTracker });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -975,14 +958,14 @@ export class NetworkController extends EventEmitter {
|
||||
* @param rpcUrl - The URL of the RPC endpoint that represents the network.
|
||||
* @param chainId - The chain ID of the network (as per EIP-155).
|
||||
*/
|
||||
_configureStandardProvider(rpcUrl: string, chainId: ChainId): void {
|
||||
log.info('NetworkController - configureStandardProvider', rpcUrl);
|
||||
#configureStandardProvider(rpcUrl: string, chainId: ChainId): void {
|
||||
log.info('NetworkController - #configureStandardProvider', rpcUrl);
|
||||
const { provider, blockTracker } = createNetworkClient({
|
||||
chainId,
|
||||
rpcUrl,
|
||||
type: NetworkClientType.Custom,
|
||||
});
|
||||
this._setProviderAndBlockTracker({ provider, blockTracker });
|
||||
this.#setProviderAndBlockTracker({ provider, blockTracker });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -994,7 +977,7 @@ export class NetworkController extends EventEmitter {
|
||||
* @param args.provider - The provider.
|
||||
* @param args.blockTracker - The block tracker.
|
||||
*/
|
||||
_setProviderAndBlockTracker({
|
||||
#setProviderAndBlockTracker({
|
||||
provider,
|
||||
blockTracker,
|
||||
}: {
|
||||
@ -1002,21 +985,21 @@ export class NetworkController extends EventEmitter {
|
||||
blockTracker: PollingBlockTracker;
|
||||
}): void {
|
||||
// update or initialize proxies
|
||||
if (this._providerProxy) {
|
||||
this._providerProxy.setTarget(provider);
|
||||
if (this.#providerProxy) {
|
||||
this.#providerProxy.setTarget(provider);
|
||||
} else {
|
||||
this._providerProxy = createSwappableProxy(provider);
|
||||
this.#providerProxy = createSwappableProxy(provider);
|
||||
}
|
||||
if (this._blockTrackerProxy) {
|
||||
this._blockTrackerProxy.setTarget(blockTracker);
|
||||
if (this.#blockTrackerProxy) {
|
||||
this.#blockTrackerProxy.setTarget(blockTracker);
|
||||
} else {
|
||||
this._blockTrackerProxy = createEventEmitterProxy(blockTracker, {
|
||||
this.#blockTrackerProxy = createEventEmitterProxy(blockTracker, {
|
||||
eventFilter: 'skipInternal',
|
||||
});
|
||||
}
|
||||
// set new provider and blockTracker
|
||||
this._provider = provider;
|
||||
this._blockTracker = blockTracker;
|
||||
this.#provider = provider;
|
||||
this.#blockTracker = blockTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1105,7 +1088,7 @@ export class NetworkController extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
const networkConfigurations = this.networkConfigurationsStore.getState();
|
||||
const { networkConfigurations } = this.store.getState();
|
||||
const newNetworkConfiguration = {
|
||||
rpcUrl,
|
||||
chainId,
|
||||
@ -1120,16 +1103,18 @@ export class NetworkController extends EventEmitter {
|
||||
)?.id;
|
||||
|
||||
const newNetworkConfigurationId = oldNetworkConfigurationId || uuid();
|
||||
this.networkConfigurationsStore.putState({
|
||||
this.store.updateState({
|
||||
networkConfigurations: {
|
||||
...networkConfigurations,
|
||||
[newNetworkConfigurationId]: {
|
||||
...newNetworkConfiguration,
|
||||
id: newNetworkConfigurationId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!oldNetworkConfigurationId) {
|
||||
this._trackMetaMetricsEvent({
|
||||
this.#trackMetaMetricsEvent({
|
||||
event: 'Custom Network Added',
|
||||
category: MetaMetricsEventCategory.Network,
|
||||
referrer: {
|
||||
@ -1160,9 +1145,11 @@ export class NetworkController extends EventEmitter {
|
||||
networkConfigurationId: NetworkConfigurationId,
|
||||
): void {
|
||||
const networkConfigurations = {
|
||||
...this.networkConfigurationsStore.getState(),
|
||||
...this.store.getState().networkConfigurations,
|
||||
};
|
||||
delete networkConfigurations[networkConfigurationId];
|
||||
this.networkConfigurationsStore.putState(networkConfigurations);
|
||||
this.store.updateState({
|
||||
networkConfigurations,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ describe('buildSnapRestrictedMethodSpecifications', () => {
|
||||
getSnap: () => undefined,
|
||||
getSnapRpcHandler: () => undefined,
|
||||
getSnapState: () => undefined,
|
||||
showConfirmation: () => undefined,
|
||||
updateSnapState: () => undefined,
|
||||
};
|
||||
|
||||
|
@ -2,36 +2,13 @@ import { strict as assert } from 'assert';
|
||||
import sinon from 'sinon';
|
||||
import { ControllerMessenger } from '@metamask/base-controller';
|
||||
import { TokenListController } from '@metamask/assets-controllers';
|
||||
import { CHAIN_IDS } from '../../../shared/constants/network';
|
||||
import PreferencesController from './preferences';
|
||||
import { NetworkController } from './network';
|
||||
|
||||
describe('preferences controller', function () {
|
||||
let preferencesController;
|
||||
let network;
|
||||
let currentChainId;
|
||||
let provider;
|
||||
let tokenListController;
|
||||
|
||||
beforeEach(function () {
|
||||
const sandbox = sinon.createSandbox();
|
||||
currentChainId = CHAIN_IDS.MAINNET;
|
||||
const networkControllerProviderConfig = {
|
||||
getAccounts: () => undefined,
|
||||
};
|
||||
const networkControllerMessenger = new ControllerMessenger();
|
||||
network = new NetworkController({
|
||||
infuraProjectId: 'foo',
|
||||
messenger: networkControllerMessenger,
|
||||
state: {
|
||||
provider: {
|
||||
type: 'mainnet',
|
||||
chainId: currentChainId,
|
||||
},
|
||||
},
|
||||
});
|
||||
network.initializeProvider(networkControllerProviderConfig);
|
||||
provider = network.getProviderAndBlockTracker().provider;
|
||||
const tokenListMessenger = new ControllerMessenger().getRestricted({
|
||||
name: 'TokenListController',
|
||||
});
|
||||
@ -43,14 +20,8 @@ describe('preferences controller', function () {
|
||||
messenger: tokenListMessenger,
|
||||
});
|
||||
|
||||
sandbox
|
||||
.stub(network, '_getLatestBlock')
|
||||
.callsFake(() => Promise.resolve({}));
|
||||
|
||||
preferencesController = new PreferencesController({
|
||||
initLangCode: 'en_US',
|
||||
network,
|
||||
provider,
|
||||
tokenListController,
|
||||
onInfuraIsBlocked: sinon.spy(),
|
||||
onInfuraIsUnblocked: sinon.spy(),
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { errorCodes } from 'eth-rpc-errors';
|
||||
import { detectSIWE } from '@metamask/controller-utils';
|
||||
import { isValidAddress } from 'ethereumjs-util';
|
||||
|
||||
import { MESSAGE_TYPE, ORIGIN_METAMASK } from '../../../shared/constants/app';
|
||||
import { TransactionStatus } from '../../../shared/constants/transaction';
|
||||
import { SECOND } from '../../../shared/constants/time';
|
||||
@ -168,8 +170,17 @@ export default function createRPCMethodTrackingMiddleware({
|
||||
if (event === MetaMetricsEventName.SignatureRequested) {
|
||||
eventProperties.signature_type = method;
|
||||
|
||||
const data = req?.params?.[0];
|
||||
const from = req?.params?.[1];
|
||||
// In personal messages the first param is data while in typed messages second param is data
|
||||
// if condition below is added to ensure that the right params are captured as data and address.
|
||||
let data;
|
||||
let from;
|
||||
if (isValidAddress(req?.params?.[1])) {
|
||||
data = req?.params?.[0];
|
||||
from = req?.params?.[1];
|
||||
} else {
|
||||
data = req?.params?.[1];
|
||||
from = req?.params?.[0];
|
||||
}
|
||||
const paramsExamplePassword = req?.params?.[2];
|
||||
|
||||
const msgData = {
|
||||
|
@ -383,5 +383,66 @@ describe('createRPCMethodTrackingMiddleware', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when signature requests are received', () => {
|
||||
let securityProviderReq, fnHandler;
|
||||
beforeEach(() => {
|
||||
securityProviderReq = jest.fn().mockReturnValue(() =>
|
||||
Promise.resolve({
|
||||
flagAsDangerous: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
fnHandler = createRPCMethodTrackingMiddleware({
|
||||
trackEvent,
|
||||
getMetricsState,
|
||||
rateLimitSeconds: 1,
|
||||
securityProviderRequest: securityProviderReq,
|
||||
});
|
||||
});
|
||||
it(`should pass correct data for personal sign`, async () => {
|
||||
const req = {
|
||||
method: 'personal_sign',
|
||||
params: [
|
||||
'0x4578616d706c652060706572736f6e616c5f7369676e60206d657373616765',
|
||||
'0x8eeee1781fd885ff5ddef7789486676961873d12',
|
||||
'Example password',
|
||||
],
|
||||
jsonrpc: '2.0',
|
||||
id: 1142196570,
|
||||
origin: 'https://metamask.github.io',
|
||||
tabId: 1048582817,
|
||||
};
|
||||
const res = { id: 1142196570, jsonrpc: '2.0' };
|
||||
const { next } = getNext();
|
||||
|
||||
await fnHandler(req, res, next);
|
||||
|
||||
expect(securityProviderReq).toHaveBeenCalledTimes(1);
|
||||
const call = securityProviderReq.mock.calls[0][0];
|
||||
expect(call.msgParams.data).toStrictEqual(req.params[0]);
|
||||
});
|
||||
it(`should pass correct data for typed sign`, async () => {
|
||||
const req = {
|
||||
method: 'eth_signTypedData_v4',
|
||||
params: [
|
||||
'0x8eeee1781fd885ff5ddef7789486676961873d12',
|
||||
'{"domain":{"chainId":"5","name":"Ether Mail","verifyingContract":"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC","version":"1"},"message":{"contents":"Hello, Bob!","from":{"name":"Cow","wallets":["0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826","0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"]},"to":[{"name":"Bob","wallets":["0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB","0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57","0xB0B0b0b0b0b0B000000000000000000000000000"]}]},"primaryType":"Mail","types":{"EIP712Domain":[{"name":"name","type":"string"},{"name":"version","type":"string"},{"name":"chainId","type":"uint256"},{"name":"verifyingContract","type":"address"}],"Group":[{"name":"name","type":"string"},{"name":"members","type":"Person[]"}],"Mail":[{"name":"from","type":"Person"},{"name":"to","type":"Person[]"},{"name":"contents","type":"string"}],"Person":[{"name":"name","type":"string"},{"name":"wallets","type":"address[]"}]}}',
|
||||
],
|
||||
jsonrpc: '2.0',
|
||||
id: 1142196571,
|
||||
origin: 'https://metamask.github.io',
|
||||
tabId: 1048582817,
|
||||
};
|
||||
const res = { id: 1142196571, jsonrpc: '2.0' };
|
||||
const { next } = getNext();
|
||||
|
||||
await fnHandler(req, res, next);
|
||||
|
||||
expect(securityProviderReq).toHaveBeenCalledTimes(1);
|
||||
const call = securityProviderReq.mock.calls[0][0];
|
||||
expect(call.msgParams.data).toStrictEqual(req.params[1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -113,7 +113,7 @@ async function switchEthereumChainHandler(
|
||||
approvedRequestData.type !== NETWORK_TYPES.LOCALHOST &&
|
||||
approvedRequestData.type !== NETWORK_TYPES.LINEA_TESTNET
|
||||
) {
|
||||
setProviderType(approvedRequestData.type);
|
||||
await setProviderType(approvedRequestData.type);
|
||||
} else {
|
||||
await setActiveNetwork(approvedRequestData.id);
|
||||
}
|
||||
|
@ -544,7 +544,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
messenger: currencyRateMessenger,
|
||||
state: {
|
||||
...initState.CurrencyController,
|
||||
nativeCurrency: this.networkController.providerStore.getState().ticker,
|
||||
nativeCurrency: this.networkController.store.getState().provider.ticker,
|
||||
},
|
||||
});
|
||||
|
||||
@ -981,8 +981,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
getNetworkId: () => this.networkController.store.getState().networkId,
|
||||
getNetworkStatus: () =>
|
||||
this.networkController.store.getState().networkStatus,
|
||||
onNetworkStateChange: (listener) =>
|
||||
this.networkController.networkIdStore.subscribe(listener),
|
||||
onNetworkStateChange: (listener) => {
|
||||
let previousNetworkId =
|
||||
this.networkController.store.getState().networkId;
|
||||
this.networkController.store.subscribe((state) => {
|
||||
if (previousNetworkId !== state.networkId) {
|
||||
listener();
|
||||
previousNetworkId = state.networkId;
|
||||
}
|
||||
});
|
||||
},
|
||||
getCurrentChainId: () =>
|
||||
this.networkController.store.getState().provider.chainId,
|
||||
preferencesStore: this.preferencesController.store,
|
||||
@ -1166,6 +1174,9 @@ export default class MetamaskController extends EventEmitter {
|
||||
preferencesController: this.preferencesController,
|
||||
getState: this.getState.bind(this),
|
||||
securityProviderRequest: this.securityProviderRequest.bind(this),
|
||||
metricsEvent: this.metaMetricsController.trackEvent.bind(
|
||||
this.metaMetricsController,
|
||||
),
|
||||
});
|
||||
|
||||
this.swapsController = new SwapsController({
|
||||
@ -1524,12 +1535,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
this.controllerMessenger,
|
||||
'SnapController:getSnapState',
|
||||
),
|
||||
showConfirmation: (origin, confirmationData) =>
|
||||
this.approvalController.addAndShowApprovalRequest({
|
||||
origin,
|
||||
type: MESSAGE_TYPE.SNAP_DIALOG_CONFIRMATION,
|
||||
requestData: confirmationData,
|
||||
}),
|
||||
showDialog: (origin, type, content, placeholder) =>
|
||||
this.approvalController.addAndShowApprovalRequest({
|
||||
origin,
|
||||
@ -3237,32 +3242,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
return await this.txController.newUnapprovedTransaction(txParams, req);
|
||||
}
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
/**
|
||||
* Gets an "app key" corresponding to an Ethereum address. An app key is more
|
||||
* or less an addrdess hashed together with some string, in this case a
|
||||
* subject identifier / origin.
|
||||
*
|
||||
* @todo Figure out a way to derive app keys that doesn't depend on the user's
|
||||
* Ethereum addresses.
|
||||
* @param {string} subject - The identifier of the subject whose app key to
|
||||
* retrieve.
|
||||
* @param {string} [requestedAccount] - The account whose app key to retrieve.
|
||||
* The first account in the keyring will be used by default.
|
||||
*/
|
||||
async getAppKeyForSubject(subject, requestedAccount) {
|
||||
let account;
|
||||
|
||||
if (requestedAccount) {
|
||||
account = requestedAccount;
|
||||
} else {
|
||||
[account] = await this.keyringController.getAccounts();
|
||||
}
|
||||
|
||||
return this.keyringController.exportAppKeyForAddress(account, subject);
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
// eth_decrypt methods
|
||||
|
||||
/**
|
||||
@ -3868,7 +3847,6 @@ export default class MetamaskController extends EventEmitter {
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
engine.push(
|
||||
createSnapMethodMiddleware(subjectType === SubjectType.Snap, {
|
||||
getAppKey: this.getAppKeyForSubject.bind(this, origin),
|
||||
getUnlockPromise: this.appStateController.getUnlockPromise.bind(
|
||||
this.appStateController,
|
||||
),
|
||||
|
@ -17,7 +17,7 @@ import {
|
||||
TextVariant,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import GasDetailsItem from '../gas-details-item/gas-details-item';
|
||||
import { ConfirmGasDisplay } from '../confirm-gas-display';
|
||||
import MultiLayerFeeMessage from '../multilayer-fee-message/multi-layer-fee-message';
|
||||
import { formatCurrency } from '../../../helpers/utils/confirm-tx.util';
|
||||
|
||||
@ -111,7 +111,7 @@ export default function ApproveContentCard({
|
||||
(!isMultiLayerFeeNetwork &&
|
||||
supportsEIP1559 &&
|
||||
!renderSimulationFailureWarning ? (
|
||||
<GasDetailsItem
|
||||
<ConfirmGasDisplay
|
||||
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||
/>
|
||||
) : (
|
||||
|
@ -0,0 +1,157 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ConfirmGasDisplay should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="transaction-detail-item"
|
||||
>
|
||||
<div
|
||||
class="transaction-detail-item__row"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--display-flex box--flex-direction-row box--flex-wrap-nowrap box--align-items-center typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
<div
|
||||
class="box box--display-flex box--flex-direction-row"
|
||||
>
|
||||
<div
|
||||
class="box box--margin-right-1 box--flex-direction-row"
|
||||
>
|
||||
Gas
|
||||
</div>
|
||||
<span
|
||||
class="gas-details-item-title__estimate"
|
||||
>
|
||||
(
|
||||
estimated
|
||||
)
|
||||
</span>
|
||||
<div
|
||||
class="info-tooltip"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-1"
|
||||
class="info-tooltip__tooltip-container"
|
||||
data-original-title="null"
|
||||
data-tooltipped=""
|
||||
style="display: inline;"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 10 10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
|
||||
fill="var(--color-icon-alternative)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
<div
|
||||
class="transaction-detail-item__detail-values"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
|
||||
>
|
||||
<div
|
||||
class="gas-details-item__currency-container"
|
||||
>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
<div
|
||||
class="gas-details-item__currency-container"
|
||||
>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0 ETH"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0
|
||||
</span>
|
||||
<span
|
||||
class="currency-display-component__suffix"
|
||||
>
|
||||
ETH
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="transaction-detail-item__row"
|
||||
>
|
||||
<div>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography gas-timing gas-timing--positive typography--h7 typography--weight-normal typography--style-normal typography--color-text-default"
|
||||
>
|
||||
Maybe in 1 seconds
|
||||
</h6>
|
||||
</div>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
|
||||
>
|
||||
<div
|
||||
class="box gas-details-item__gasfee-label box--display-inline-flex box--flex-direction-row"
|
||||
>
|
||||
<div
|
||||
class="box box--margin-right-1 box--flex-direction-row"
|
||||
>
|
||||
<strong>
|
||||
Max fee:
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
class="gas-details-item__currency-container"
|
||||
>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0 ETH"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0
|
||||
</span>
|
||||
<span
|
||||
class="currency-display-component__suffix"
|
||||
>
|
||||
ETH
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
43
ui/components/app/confirm-gas-display/confirm-gas-display.js
Normal file
43
ui/components/app/confirm-gas-display/confirm-gas-display.js
Normal file
@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import {
|
||||
checkNetworkAndAccountSupports1559,
|
||||
txDataSelector,
|
||||
} from '../../../selectors';
|
||||
import { isLegacyTransaction } from '../../../helpers/utils/transactions.util';
|
||||
import GasDetailsItem from '../gas-details-item';
|
||||
import { getCurrentDraftTransaction } from '../../../ducks/send';
|
||||
import { TransactionEnvelopeType } from '../../../../shared/constants/transaction';
|
||||
import { ConfirmLegacyGasDisplay } from './confirm-legacy-gas-display';
|
||||
|
||||
const ConfirmGasDisplay = ({ userAcknowledgedGasMissing = false }) => {
|
||||
const { txParams } = useSelector((state) => txDataSelector(state));
|
||||
|
||||
const draftTransaction = useSelector(getCurrentDraftTransaction);
|
||||
const transactionType = draftTransaction?.transactionType;
|
||||
let isLegacyTxn;
|
||||
if (transactionType) {
|
||||
isLegacyTxn = transactionType === TransactionEnvelopeType.legacy;
|
||||
} else {
|
||||
isLegacyTxn = isLegacyTransaction(txParams);
|
||||
}
|
||||
|
||||
const networkAndAccountSupports1559 = useSelector(
|
||||
checkNetworkAndAccountSupports1559,
|
||||
);
|
||||
const supportsEIP1559 = networkAndAccountSupports1559 && !isLegacyTxn;
|
||||
|
||||
return supportsEIP1559 ? (
|
||||
<GasDetailsItem userAcknowledgedGasMissing={userAcknowledgedGasMissing} />
|
||||
) : (
|
||||
<ConfirmLegacyGasDisplay />
|
||||
);
|
||||
};
|
||||
|
||||
ConfirmGasDisplay.propTypes = {
|
||||
userAcknowledgedGasMissing: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default ConfirmGasDisplay;
|
@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
import { screen } from '@testing-library/react';
|
||||
|
||||
import { GasEstimateTypes } from '../../../../shared/constants/gas';
|
||||
import mockEstimates from '../../../../test/data/mock-estimates.json';
|
||||
import mockState from '../../../../test/data/mock-state.json';
|
||||
import { renderWithProvider } from '../../../../test/jest';
|
||||
import configureStore from '../../../store/store';
|
||||
|
||||
import { GasFeeContextProvider } from '../../../contexts/gasFee';
|
||||
import ConfirmGasDisplay from './confirm-gas-display';
|
||||
|
||||
jest.mock('../../../store/actions', () => ({
|
||||
disconnectGasFeeEstimatePoller: jest.fn(),
|
||||
getGasFeeEstimatesAndStartPolling: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
addPollingTokenToAppState: jest.fn(),
|
||||
getGasFeeTimeEstimate: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
const render = ({ transactionProp = {}, contextProps = {} } = {}) => {
|
||||
const store = configureStore({
|
||||
...mockState,
|
||||
...contextProps,
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
accounts: {
|
||||
[mockState.metamask.selectedAddress]: {
|
||||
address: mockState.metamask.selectedAddress,
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
gasFeeEstimates: mockEstimates[GasEstimateTypes.feeMarket],
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(
|
||||
<GasFeeContextProvider transaction={transactionProp}>
|
||||
<ConfirmGasDisplay />
|
||||
</GasFeeContextProvider>,
|
||||
store,
|
||||
);
|
||||
};
|
||||
|
||||
describe('ConfirmGasDisplay', () => {
|
||||
it('should match snapshot', async () => {
|
||||
const { container } = render();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
it('should render gas display labels for EIP1559 transcations', () => {
|
||||
render({
|
||||
transactionProp: {
|
||||
txParams: {
|
||||
gas: '0x5208',
|
||||
maxFeePerGas: '0x59682f10',
|
||||
maxPriorityFeePerGas: '0x59682f00',
|
||||
},
|
||||
userFeeLevel: 'medium',
|
||||
},
|
||||
});
|
||||
expect(screen.queryByText('Gas')).toBeInTheDocument();
|
||||
expect(screen.queryByText('(estimated)')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
||||
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
|
||||
});
|
||||
it('should render gas display labels for legacy transcations', () => {
|
||||
render({
|
||||
contextProps: {
|
||||
metamask: {
|
||||
networkDetails: {
|
||||
EIPS: {
|
||||
1559: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
confirmTransaction: {
|
||||
txData: {
|
||||
id: 8393540981007587,
|
||||
status: 'unapproved',
|
||||
chainId: '0x5',
|
||||
txParams: {
|
||||
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
|
||||
to: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
|
||||
value: '0x0',
|
||||
gas: '0x5208',
|
||||
gasPrice: '0x3b9aca00',
|
||||
type: '0x0',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(screen.queryByText('Estimated gas fee')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
||||
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
@ -0,0 +1,9 @@
|
||||
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
|
||||
import { ConfirmLegacyGasDisplay } from '.';
|
||||
|
||||
# Confirm Legacy Gas Display
|
||||
Confirm Legacy Gas Display is used on confirmation screen and send screen to display gas details for legacy transaction.
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-app-ConfirmLegacyGasDisplay--default-story" />
|
||||
</Canvas>
|
@ -0,0 +1,124 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ConfirmLegacyGasDisplay should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="transaction-detail-item"
|
||||
>
|
||||
<div
|
||||
class="transaction-detail-item__row"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--display-flex box--flex-direction-row box--flex-wrap-nowrap box--align-items-center typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
Estimated gas fee
|
||||
<div
|
||||
class="info-tooltip"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-1"
|
||||
class="info-tooltip__tooltip-container"
|
||||
data-original-title="null"
|
||||
data-tooltipped=""
|
||||
style="display: inline;"
|
||||
tabindex="0"
|
||||
>
|
||||
<svg
|
||||
viewBox="0 0 10 10"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M5 0C2.2 0 0 2.2 0 5s2.2 5 5 5 5-2.2 5-5-2.2-5-5-5zm0 2c.4 0 .7.3.7.7s-.3.7-.7.7-.7-.2-.7-.6.3-.8.7-.8zm.7 6H4.3V4.3h1.5V8z"
|
||||
fill="var(--color-icon-alternative)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
<div
|
||||
class="transaction-detail-item__detail-values"
|
||||
>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography typography--h6 typography--weight-normal typography--style-normal typography--color-text-alternative"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0.000021"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0.000021
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0.000021 ETH"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0.000021
|
||||
</span>
|
||||
<span
|
||||
class="currency-display-component__suffix"
|
||||
>
|
||||
ETH
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="transaction-detail-item__row"
|
||||
>
|
||||
<div />
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
|
||||
>
|
||||
<strong>
|
||||
Max fee:
|
||||
</strong>
|
||||
<div>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0.000021 ETH"
|
||||
>
|
||||
<span
|
||||
class="currency-display-component__prefix"
|
||||
/>
|
||||
<span
|
||||
class="currency-display-component__text"
|
||||
>
|
||||
0.000021
|
||||
</span>
|
||||
<span
|
||||
class="currency-display-component__suffix"
|
||||
>
|
||||
ETH
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ConfirmLegacyGasDisplay should match snapshot 2`] = `<div />`;
|
@ -0,0 +1,149 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import {
|
||||
getIsMainnet,
|
||||
getPreferences,
|
||||
getUnapprovedTransactions,
|
||||
getUseCurrencyRateCheck,
|
||||
transactionFeeSelector,
|
||||
txDataSelector,
|
||||
} from '../../../../selectors';
|
||||
import { PRIMARY, SECONDARY } from '../../../../helpers/constants/common';
|
||||
|
||||
import TransactionDetailItem from '../../transaction-detail-item';
|
||||
import UserPreferencedCurrencyDisplay from '../../user-preferenced-currency-display';
|
||||
import InfoTooltip from '../../../ui/info-tooltip';
|
||||
import LoadingHeartBeat from '../../../ui/loading-heartbeat';
|
||||
import { Text } from '../../../component-library/text';
|
||||
import {
|
||||
FONT_STYLE,
|
||||
TextVariant,
|
||||
TextColor,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import { useDraftTransactionGasValues } from '../../../../hooks/useDraftTransactionGasValues';
|
||||
|
||||
const renderHeartBeatIfNotInTest = () =>
|
||||
process.env.IN_TEST ? null : <LoadingHeartBeat />;
|
||||
|
||||
const ConfirmLegacyGasDisplay = () => {
|
||||
const t = useI18nContext();
|
||||
|
||||
// state selectors
|
||||
const isMainnet = useSelector(getIsMainnet);
|
||||
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
|
||||
const { useNativeCurrencyAsPrimaryCurrency } = useSelector(getPreferences);
|
||||
const unapprovedTxs = useSelector(getUnapprovedTransactions);
|
||||
const { transactionData } = useDraftTransactionGasValues();
|
||||
const txData = useSelector((state) => txDataSelector(state));
|
||||
const { id: transactionId, dappSuggestedGasFees } = txData;
|
||||
const transaction = Object.keys(transactionData).length
|
||||
? transactionData
|
||||
: unapprovedTxs[transactionId] || {};
|
||||
const { hexMinimumTransactionFee, hexMaximumTransactionFee } = useSelector(
|
||||
(state) => transactionFeeSelector(state, transaction),
|
||||
);
|
||||
|
||||
return (
|
||||
<TransactionDetailItem
|
||||
key="legacy-gas-details"
|
||||
detailTitle={
|
||||
dappSuggestedGasFees ? (
|
||||
<>
|
||||
{t('transactionDetailGasHeading')}
|
||||
<InfoTooltip
|
||||
contentText={t('transactionDetailDappGasTooltip')}
|
||||
position="top"
|
||||
>
|
||||
<i className="fa fa-info-circle" />
|
||||
</InfoTooltip>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t('transactionDetailGasHeading')}
|
||||
<InfoTooltip
|
||||
contentText={
|
||||
<>
|
||||
<p>
|
||||
{t('transactionDetailGasTooltipIntro', [
|
||||
isMainnet ? t('networkNameEthereum') : '',
|
||||
])}
|
||||
</p>
|
||||
<p>{t('transactionDetailGasTooltipExplanation')}</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('transactionDetailGasTooltipConversion')}
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
position="top"
|
||||
>
|
||||
<i className="fa fa-info-circle" />
|
||||
</InfoTooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
detailText={
|
||||
useCurrencyRateCheck && (
|
||||
<div>
|
||||
{renderHeartBeatIfNotInTest()}
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={SECONDARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
detailTotal={
|
||||
<div>
|
||||
{renderHeartBeatIfNotInTest()}
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
numberOfDecimals={6}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
subText={
|
||||
<>
|
||||
<strong key="editGasSubTextFeeLabel">
|
||||
{t('editGasSubTextFeeLabel')}
|
||||
</strong>
|
||||
<div key="editGasSubTextFeeValue">
|
||||
{renderHeartBeatIfNotInTest()}
|
||||
<UserPreferencedCurrencyDisplay
|
||||
key="editGasSubTextFeeAmount"
|
||||
type={PRIMARY}
|
||||
value={hexMaximumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
subTitle={
|
||||
<>
|
||||
{dappSuggestedGasFees && (
|
||||
<Text
|
||||
variant={TextVariant.bodySm}
|
||||
fontStyle={FONT_STYLE.ITALIC}
|
||||
color={TextColor.textAlternative}
|
||||
as="h6"
|
||||
>
|
||||
{t('transactionDetailDappGasMoreInfo')}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmLegacyGasDisplay;
|
@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import mockState from '../../../../../test/data/mock-state.json';
|
||||
import configureStore from '../../../../store/store';
|
||||
|
||||
import README from './README.mdx';
|
||||
import ConfirmLegacyGasDisplay from './confirm-legacy-gas-display';
|
||||
|
||||
const store = configureStore(mockState);
|
||||
|
||||
export default {
|
||||
title: 'Components/App/ConfirmLegacyGasDisplay',
|
||||
|
||||
component: ConfirmLegacyGasDisplay,
|
||||
decorators: [(story) => <Provider store={store}>{story()}</Provider>],
|
||||
parameters: {
|
||||
docs: {
|
||||
page: README,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = () => {
|
||||
return <ConfirmLegacyGasDisplay />;
|
||||
};
|
||||
|
||||
DefaultStory.storyName = 'Default';
|
@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
import { screen, waitFor } from '@testing-library/react';
|
||||
|
||||
import mockState from '../../../../../test/data/mock-state.json';
|
||||
import { renderWithProvider } from '../../../../../test/jest';
|
||||
import configureStore from '../../../../store/store';
|
||||
|
||||
import ConfirmLegacyGasDisplay from './confirm-legacy-gas-display';
|
||||
|
||||
const render = ({ contextProps } = {}) => {
|
||||
const store = configureStore({
|
||||
...mockState,
|
||||
...contextProps,
|
||||
metamask: {
|
||||
...mockState.metamask,
|
||||
accounts: {
|
||||
[mockState.metamask.selectedAddress]: {
|
||||
address: mockState.metamask.selectedAddress,
|
||||
balance: '0x1F4',
|
||||
},
|
||||
},
|
||||
unapprovedTxs: {
|
||||
8393540981007587: {
|
||||
...mockState.metamask.unapprovedTxs[8393540981007587],
|
||||
txParams: {
|
||||
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
|
||||
to: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
|
||||
value: '0x0',
|
||||
gas: '0x5208',
|
||||
gasPrice: '0x3b9aca00',
|
||||
type: '0x0',
|
||||
},
|
||||
},
|
||||
},
|
||||
preferences: {
|
||||
useNativeCurrencyAsPrimaryCurrency: true,
|
||||
},
|
||||
},
|
||||
confirmTransaction: {
|
||||
txData: {
|
||||
id: 8393540981007587,
|
||||
status: 'unapproved',
|
||||
chainId: '0x5',
|
||||
txParams: {
|
||||
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
|
||||
to: '0xc42edfcc21ed14dda456aa0756c153f7985d8813',
|
||||
value: '0x0',
|
||||
gas: '0x5208',
|
||||
gasPrice: '0x3b9aca00',
|
||||
type: '0x0',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return renderWithProvider(<ConfirmLegacyGasDisplay />, store);
|
||||
};
|
||||
|
||||
describe('ConfirmLegacyGasDisplay', () => {
|
||||
it('should match snapshot', async () => {
|
||||
const { container } = render();
|
||||
await waitFor(() => {
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render label', async () => {
|
||||
render();
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Estimated gas fee')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
||||
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render gas fee details', async () => {
|
||||
render();
|
||||
await waitFor(() => {
|
||||
expect(screen.queryAllByTitle('0.000021 ETH').length).toBeGreaterThan(0);
|
||||
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render label and gas details with draftTransaction', async () => {
|
||||
render({
|
||||
send: {
|
||||
currentTransactionUUID: '1d40b578-6184-4607-8513-762c24d0a19b',
|
||||
draftTransactions: {
|
||||
'1d40b578-6184-4607-8513-762c24d0a19b': {
|
||||
gas: {
|
||||
error: null,
|
||||
gasLimit: '0x5208',
|
||||
gasPrice: '0x3b9aca00',
|
||||
gasTotal: '0x157c9fbb9a000',
|
||||
maxFeePerGas: '0x0',
|
||||
maxPriorityFeePerGas: '0x0',
|
||||
wasManuallyEdited: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Estimated gas fee')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Max fee:')).toBeInTheDocument();
|
||||
expect(screen.queryAllByText('ETH').length).toBeGreaterThan(0);
|
||||
expect(screen.queryAllByTitle('0.000021 ETH').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default as ConfirmLegacyGasDisplay } from './confirm-legacy-gas-display';
|
1
ui/components/app/confirm-gas-display/index.js
Normal file
1
ui/components/app/confirm-gas-display/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default as ConfirmGasDisplay } from './confirm-gas-display';
|
@ -5,7 +5,12 @@ import { useSelector } from 'react-redux';
|
||||
|
||||
import { TextColor } from '../../../helpers/constants/design-system';
|
||||
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
|
||||
import { getPreferences, getUseCurrencyRateCheck } from '../../../selectors';
|
||||
import {
|
||||
getPreferences,
|
||||
getUseCurrencyRateCheck,
|
||||
transactionFeeSelector,
|
||||
} from '../../../selectors';
|
||||
import { getCurrentDraftTransaction } from '../../../ducks/send';
|
||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
import { useI18nContext } from '../../../hooks/useI18nContext';
|
||||
|
||||
@ -14,10 +19,20 @@ import LoadingHeartBeat from '../../ui/loading-heartbeat';
|
||||
import GasTiming from '../gas-timing/gas-timing.component';
|
||||
import TransactionDetailItem from '../transaction-detail-item/transaction-detail-item.component';
|
||||
import UserPreferencedCurrencyDisplay from '../user-preferenced-currency-display';
|
||||
import { hexWEIToDecGWEI } from '../../../../shared/modules/conversion.utils';
|
||||
import { useDraftTransactionGasValues } from '../../../hooks/useDraftTransactionGasValues';
|
||||
import GasDetailsItemTitle from './gas-details-item-title';
|
||||
|
||||
const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
|
||||
const t = useI18nContext();
|
||||
const draftTransaction = useSelector(getCurrentDraftTransaction);
|
||||
const { transactionData } = useDraftTransactionGasValues();
|
||||
|
||||
const {
|
||||
hexMinimumTransactionFee: draftHexMinimumTransactionFee,
|
||||
hexMaximumTransactionFee: draftHexMaximumTransactionFee,
|
||||
} = useSelector((state) => transactionFeeSelector(state, transactionData));
|
||||
|
||||
const {
|
||||
estimateUsed,
|
||||
hasSimulationError,
|
||||
@ -41,7 +56,8 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
|
||||
detailTitle={<GasDetailsItemTitle />}
|
||||
detailTitleColor={TextColor.textDefault}
|
||||
detailText={
|
||||
useCurrencyRateCheck && (
|
||||
useCurrencyRateCheck &&
|
||||
Object.keys(draftTransaction).length === 0 && (
|
||||
<div className="gas-details-item__currency-container">
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
@ -57,7 +73,7 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
value={hexMinimumTransactionFee || draftHexMinimumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</div>
|
||||
@ -86,7 +102,9 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
|
||||
<UserPreferencedCurrencyDisplay
|
||||
key="editGasSubTextFeeAmount"
|
||||
type={PRIMARY}
|
||||
value={hexMaximumTransactionFee}
|
||||
value={
|
||||
hexMaximumTransactionFee || draftHexMaximumTransactionFee
|
||||
}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</div>
|
||||
@ -95,8 +113,14 @@ const GasDetailsItem = ({ userAcknowledgedGasMissing = false }) => {
|
||||
}
|
||||
subTitle={
|
||||
<GasTiming
|
||||
maxPriorityFeePerGas={maxPriorityFeePerGas.toString()}
|
||||
maxFeePerGas={maxFeePerGas.toString()}
|
||||
maxPriorityFeePerGas={(
|
||||
maxPriorityFeePerGas ||
|
||||
hexWEIToDecGWEI(transactionData.txParams.maxPriorityFeePerGas)
|
||||
).toString()}
|
||||
maxFeePerGas={(
|
||||
maxFeePerGas ||
|
||||
hexWEIToDecGWEI(transactionData.txParams.maxFeePerGas)
|
||||
).toString()}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
@ -112,11 +112,11 @@ import { TextColor, BackgroundColor } from '../../../helpers/constants/design-sy
|
||||
|
||||
### Font Weight
|
||||
|
||||
Use the `fontWeight` prop and the `FONT_WEIGHT` object from `./ui/helpers/constants/design-system.js` to change the font weight of the `Text` component. There are 3 font weights:
|
||||
Use the `fontWeight` prop and the `FontWeight` enum from `./ui/helpers/constants/design-system.js` to change the font weight of the `Text` component. There are 3 font weights:
|
||||
|
||||
- `FONT_WEIGHT.NORMAL` = `normal` || `400`
|
||||
- `FONT_WEIGHT.MEDIUM` = `medium` || `500`
|
||||
- `FONT_WEIGHT.BOLD` = `bold` || `700`
|
||||
- `FontWeight.Normal` = `normal` || `400`
|
||||
- `FontWeight.Medium` = `medium` || `500`
|
||||
- `FontWeight.Bold` = `bold` || `700`
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-componentlibrary-text--font-weight" />
|
||||
@ -124,25 +124,25 @@ Use the `fontWeight` prop and the `FONT_WEIGHT` object from `./ui/helpers/consta
|
||||
|
||||
```jsx
|
||||
import { Text } from '../../component-library';
|
||||
import { FONT_WEIGHT } from '../../../helpers/constants/design-system';
|
||||
import { FontWeight } from '../../../helpers/constants/design-system';
|
||||
|
||||
<Text fontWeight={FONT_WEIGHT.NORMAL}>
|
||||
<Text fontWeight={FontWeight.Normal}>
|
||||
normal
|
||||
</Text>
|
||||
<Text fontWeight={FONT_WEIGHT.MEDIUM}>
|
||||
<Text fontWeight={FontWeight.Medium}>
|
||||
medium
|
||||
</Text>
|
||||
<Text fontWeight={FONT_WEIGHT.BOLD}>
|
||||
<Text fontWeight={FontWeight.Bold}>
|
||||
bold
|
||||
</Text>
|
||||
```
|
||||
|
||||
### Font Style
|
||||
|
||||
Use the `fontStyle` prop and the `FONT_STYLE` object from `./ui/helpers/constants/design-system.js` to change the font style of the `Text` component. There are 2 font styles:
|
||||
Use the `fontStyle` prop and the `FontStyle` enum from `./ui/helpers/constants/design-system.js` to change the font style of the `Text` component. There are 2 font styles:
|
||||
|
||||
- `FONT_STYLE.NORMAL`
|
||||
- `FONT_STYLE.ITALIC`
|
||||
- `FontStyle.Normal`
|
||||
- `FontStyle.Italic`
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-componentlibrary-text--font-style" />
|
||||
@ -150,19 +150,19 @@ Use the `fontStyle` prop and the `FONT_STYLE` object from `./ui/helpers/constant
|
||||
|
||||
```jsx
|
||||
import { Text } from '../../component-library';
|
||||
import { FONT_STYLE } from '../../../helpers/constants/design-system';
|
||||
import { FontStyle } from '../../../helpers/constants/design-system';
|
||||
|
||||
<Text fontStyle={FONT_STYLE.NORMAL}>
|
||||
<Text fontStyle={FontStyle.Normal}>
|
||||
normal
|
||||
</Text>
|
||||
<Text fontStyle={FONT_STYLE.ITALIC}>
|
||||
<Text fontStyle={FontStyle.Italic}>
|
||||
bold
|
||||
</Text>
|
||||
```
|
||||
|
||||
### Text Transform
|
||||
|
||||
Use the `textTransform` prop and the `TEXT_TRANSFORM` object from `./ui/helpers/constants/design-system.js` to change the text alignment of the `Text` component
|
||||
Use the `textTransform` prop and the `TextTransform` enum from `./ui/helpers/constants/design-system.ts` to change the text alignment of the `Text` component
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-componentlibrary-text--text-transform" />
|
||||
@ -170,22 +170,22 @@ Use the `textTransform` prop and the `TEXT_TRANSFORM` object from `./ui/helpers/
|
||||
|
||||
```jsx
|
||||
import { Text } from '../../component-library';
|
||||
import { TEXT_TRANSFORM } from '../../../helpers/constants/design-system';
|
||||
import { TextTransform } from '../../../helpers/constants/design-system';
|
||||
|
||||
<Text textAlign={TEXT_TRANSFORM.UPPERCASE}>
|
||||
<Text textAlign={TextTransform.Uppercase}>
|
||||
uppercase
|
||||
</Text>
|
||||
<Text textAlign={TEXT_TRANSFORM.LOWERCASE}>
|
||||
<Text textAlign={TextTransform.Lowercase}>
|
||||
lowercase
|
||||
</Text>
|
||||
<Text textAlign={TEXT_TRANSFORM.CAPITALIZE}>
|
||||
<Text textAlign={TextTransform.Capitalize}>
|
||||
capitalize
|
||||
</Text>
|
||||
```
|
||||
|
||||
### Text Align
|
||||
|
||||
Use the `textAlign` prop and the `TEXT_ALIGN` object from `./ui/helpers/constants/design-system.js` to change the text alignment of the `Text` component
|
||||
Use the `textAlign` prop and the `TextAlign` enum from `./ui/helpers/constants/design-system.ts` to change the text alignment of the `Text` component
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-componentlibrary-text--text-align" />
|
||||
@ -193,28 +193,28 @@ Use the `textAlign` prop and the `TEXT_ALIGN` object from `./ui/helpers/constant
|
||||
|
||||
```jsx
|
||||
import { Text } from '../../component-library';
|
||||
import { TEXT_ALIGN } from '../../../helpers/constants/design-system';
|
||||
import { TextAlign } from '../../../helpers/constants/design-system';
|
||||
|
||||
<Text textAlign={TEXT_ALIGN.LEFT}>
|
||||
<Text textAlign={TextAlign.Left}>
|
||||
left
|
||||
</Text>
|
||||
<Text textAlign={TEXT_ALIGN.CENTER}>
|
||||
<Text textAlign={TextAlign.Center}>
|
||||
center
|
||||
</Text>
|
||||
<Text textAlign={TEXT_ALIGN.RIGHT}>
|
||||
<Text textAlign={TextAlign.Right}>
|
||||
right
|
||||
</Text>
|
||||
<Text textAlign={TEXT_ALIGN.JUSTIFY}>
|
||||
<Text textAlign={TextAlign.Justify}>
|
||||
justify
|
||||
</Text>
|
||||
<Text textAlign={TEXT_ALIGN.END}>
|
||||
<Text textAlign={TextAlign.End}>
|
||||
end
|
||||
</Text>
|
||||
```
|
||||
|
||||
### Overflow Wrap
|
||||
|
||||
Use the `overflowWrap` prop and the `OVERFLOW_WRAP` object from `./ui/helpers/constants/design-system.js` to change the overflow wrap of the `Text` component
|
||||
Use the `overflowWrap` prop and the `OverflowWrap` enum from `./ui/helpers/constants/design-system.ts` to change the overflow wrap of the `Text` component
|
||||
|
||||
<Canvas>
|
||||
<Story id="components-componentlibrary-text--overflow-wrap" />
|
||||
@ -222,7 +222,7 @@ Use the `overflowWrap` prop and the `OVERFLOW_WRAP` object from `./ui/helpers/co
|
||||
|
||||
```jsx
|
||||
import { Text } from '../../component-library';
|
||||
import { OVERFLOW_WRAP } from '../../../helpers/constants/design-system';
|
||||
import { OverflowWrap } from '../../../helpers/constants/design-system';
|
||||
|
||||
<div
|
||||
style={{
|
||||
@ -231,11 +231,11 @@ import { OVERFLOW_WRAP } from '../../../helpers/constants/design-system';
|
||||
display: 'block',
|
||||
}}
|
||||
>
|
||||
<Text overflowWrap={OVERFLOW_WRAP.NORMAL}>
|
||||
{OVERFLOW_WRAP.NORMAL}: 0x39013f961c378f02c2b82a6e1d31e9812786fd9d
|
||||
<Text overflowWrap={OverflowWrap.Normal}>
|
||||
{OverflowWrap.Normal}: 0x39013f961c378f02c2b82a6e1d31e9812786fd9d
|
||||
</Text>
|
||||
<Text overflowWrap={OVERFLOW_WRAP.BREAK_WORD}>
|
||||
{OVERFLOW_WRAP.BREAK_WORD}: 0x39013f961c378f02c2b82a6e1d31e9812786fd9d
|
||||
<Text overflowWrap={OverflowWrap.BreakWord}>
|
||||
{OverflowWrap.BreakWord}: 0x39013f961c378f02c2b82a6e1d31e9812786fd9d
|
||||
</Text>
|
||||
</div>;
|
||||
```
|
||||
@ -538,14 +538,14 @@ import { TextVariant } from '../../../helpers/constants/design-system';
|
||||
|
||||
The prop name `align` has been deprecated in favor of `textAlign`
|
||||
|
||||
Values and using the `TEXT_ALIGN` object from `./ui/helpers/constants/design-system.js` remain the same
|
||||
Values using the `TextAlign` object from `./ui/helpers/constants/design-system.js` remain the same
|
||||
|
||||
```jsx
|
||||
// Before
|
||||
<Typograpghy align={TEXT_ALIGN.CENTER}>Demo</Typograpghy>;
|
||||
<Typography align={TEXT_ALIGN.CENTER}>Demo</Typography>;
|
||||
|
||||
// After
|
||||
<Text textAlign={TEXT_ALIGN.CENTER}>Demo</Text>;
|
||||
<Text textAlign={TextAlign.Center}>Demo</Text>;
|
||||
```
|
||||
|
||||
### Box Props
|
||||
|
@ -1,12 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import {
|
||||
FONT_STYLE,
|
||||
FONT_WEIGHT,
|
||||
OVERFLOW_WRAP,
|
||||
TEXT_ALIGN,
|
||||
FontStyle,
|
||||
FontWeight,
|
||||
OverflowWrap,
|
||||
TextAlign,
|
||||
TextColor,
|
||||
TEXT_TRANSFORM,
|
||||
TextTransform,
|
||||
TextVariant,
|
||||
Color,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
@ -99,9 +99,9 @@ describe('Text', () => {
|
||||
it('should render the Text with proper font weight class name', () => {
|
||||
const { getByText } = render(
|
||||
<>
|
||||
<Text fontWeight={FONT_WEIGHT.BOLD}>bold</Text>
|
||||
<Text fontWeight={FONT_WEIGHT.MEDIUM}>medium</Text>
|
||||
<Text fontWeight={FONT_WEIGHT.NORMAL}>normal</Text>
|
||||
<Text fontWeight={FontWeight.Bold}>bold</Text>
|
||||
<Text fontWeight={FontWeight.Medium}>medium</Text>
|
||||
<Text fontWeight={FontWeight.Normal}>normal</Text>
|
||||
</>,
|
||||
);
|
||||
expect(getByText('bold')).toHaveClass('mm-text--font-weight-bold');
|
||||
@ -156,8 +156,8 @@ describe('Text', () => {
|
||||
it('should render the Text with proper font style class name', () => {
|
||||
const { getByText } = render(
|
||||
<>
|
||||
<Text fontStyle={FONT_STYLE.ITALIC}>italic</Text>
|
||||
<Text fontStyle={FONT_STYLE.NORMAL}>normal</Text>
|
||||
<Text fontStyle={FontStyle.Italic}>italic</Text>
|
||||
<Text fontStyle={FontStyle.Normal}>normal</Text>
|
||||
</>,
|
||||
);
|
||||
expect(getByText('italic')).toHaveClass('mm-text--font-style-italic');
|
||||
@ -167,11 +167,11 @@ describe('Text', () => {
|
||||
it('should render the Text with proper text align class name', () => {
|
||||
const { getByText } = render(
|
||||
<>
|
||||
<Text textAlign={TEXT_ALIGN.LEFT}>left</Text>
|
||||
<Text textAlign={TEXT_ALIGN.CENTER}>center</Text>
|
||||
<Text textAlign={TEXT_ALIGN.RIGHT}>right</Text>
|
||||
<Text textAlign={TEXT_ALIGN.JUSTIFY}>justify</Text>
|
||||
<Text textAlign={TEXT_ALIGN.END}>end</Text>
|
||||
<Text textAlign={TextAlign.Left}>left</Text>
|
||||
<Text textAlign={TextAlign.Center}>center</Text>
|
||||
<Text textAlign={TextAlign.Right}>right</Text>
|
||||
<Text textAlign={TextAlign.Justify}>justify</Text>
|
||||
<Text textAlign={TextAlign.End}>end</Text>
|
||||
</>,
|
||||
);
|
||||
|
||||
@ -185,8 +185,8 @@ describe('Text', () => {
|
||||
it('should render the Text with proper overflow wrap class name', () => {
|
||||
const { getByText } = render(
|
||||
<>
|
||||
<Text overflowWrap={OVERFLOW_WRAP.BREAK_WORD}>break-word</Text>
|
||||
<Text overflowWrap={OVERFLOW_WRAP.NORMAL}>normal</Text>
|
||||
<Text overflowWrap={OverflowWrap.BreakWord}>break-word</Text>
|
||||
<Text overflowWrap={OverflowWrap.Normal}>normal</Text>
|
||||
</>,
|
||||
);
|
||||
expect(getByText('break-word')).toHaveClass(
|
||||
@ -207,9 +207,9 @@ describe('Text', () => {
|
||||
it('should render the Text with proper text transform class name', () => {
|
||||
const { getByText } = render(
|
||||
<>
|
||||
<Text textTransform={TEXT_TRANSFORM.UPPERCASE}>uppercase</Text>
|
||||
<Text textTransform={TEXT_TRANSFORM.LOWERCASE}>lowercase</Text>
|
||||
<Text textTransform={TEXT_TRANSFORM.CAPITALIZE}>capitalize</Text>
|
||||
<Text textTransform={TextTransform.Uppercase}>uppercase</Text>
|
||||
<Text textTransform={TextTransform.Lowercase}>lowercase</Text>
|
||||
<Text textTransform={TextTransform.Capitalize}>capitalize</Text>
|
||||
</>,
|
||||
);
|
||||
expect(getByText('uppercase')).toHaveClass(
|
||||
|
@ -2,7 +2,7 @@ import React, { forwardRef, Ref } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Box from '../../ui/box';
|
||||
import {
|
||||
FONT_WEIGHT,
|
||||
FontWeight,
|
||||
TextVariant,
|
||||
TextColor,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
@ -49,7 +49,7 @@ export const Text = forwardRef(function Text(
|
||||
let strongTagFontWeight;
|
||||
|
||||
if (Tag === 'strong') {
|
||||
strongTagFontWeight = FONT_WEIGHT.BOLD;
|
||||
strongTagFontWeight = FontWeight.Bold;
|
||||
}
|
||||
|
||||
const computedClassName = classnames(
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from 'react';
|
||||
import type { BoxProps } from '../../ui/box/box.d';
|
||||
import {
|
||||
FONT_WEIGHT,
|
||||
FONT_STYLE,
|
||||
FontWeight,
|
||||
FontStyle,
|
||||
TextVariant,
|
||||
TEXT_ALIGN,
|
||||
TEXT_TRANSFORM,
|
||||
OVERFLOW_WRAP,
|
||||
TextAlign,
|
||||
TextTransform,
|
||||
OverflowWrap,
|
||||
TextColor,
|
||||
Color,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
@ -70,35 +70,35 @@ export interface TextProps extends BoxProps {
|
||||
*/
|
||||
color?: TextColor | Color;
|
||||
/**
|
||||
* The font-weight of the Text component. Should use the FONT_WEIGHT object from
|
||||
* The font-weight of the Text component. Should use the FontWeight enum from
|
||||
* ./ui/helpers/constants/design-system.js
|
||||
*/
|
||||
fontWeight?: keyof typeof FONT_WEIGHT;
|
||||
fontWeight?: FontWeight;
|
||||
/**
|
||||
* The font-style of the Text component. Should use the FONT_STYLE object from
|
||||
* The font-style of the Text component. Should use the FontStyle enum from
|
||||
* ./ui/helpers/constants/design-system.js
|
||||
*/
|
||||
fontStyle?: keyof typeof FONT_STYLE;
|
||||
fontStyle?: FontStyle;
|
||||
/**
|
||||
* The textTransform of the Text component. Should use the TEXT_TRANSFORM object from
|
||||
* The textTransform of the Text component. Should use the TextTransform enum from
|
||||
* ./ui/helpers/constants/design-system.js
|
||||
*/
|
||||
textTransform?: keyof typeof TEXT_TRANSFORM;
|
||||
textTransform?: TextTransform;
|
||||
/**
|
||||
* The text-align of the Text component. Should use the TEXT_ALIGN object from
|
||||
* The text-align of the Text component. Should use the TextAlign enum from
|
||||
* ./ui/helpers/constants/design-system.js
|
||||
*/
|
||||
textAlign?: keyof typeof TEXT_ALIGN;
|
||||
textAlign?: TextAlign;
|
||||
/**
|
||||
* Change the dir (direction) global attribute of text to support the direction a language is written
|
||||
* Possible values: `LEFT_TO_RIGHT` (default), `RIGHT_TO_LEFT`, `AUTO` (user agent decides)
|
||||
*/
|
||||
textDirection?: TextDirection;
|
||||
/**
|
||||
* The overflow-wrap of the Text component. Should use the OVERFLOW_WRAP object from
|
||||
* The overflow-wrap of the Text component. Should use the OverflowWrap enum from
|
||||
* ./ui/helpers/constants/design-system.js
|
||||
*/
|
||||
overflowWrap?: keyof typeof OVERFLOW_WRAP;
|
||||
overflowWrap?: OverflowWrap;
|
||||
/**
|
||||
* Used for long strings that can be cut off...
|
||||
*/
|
||||
|
@ -0,0 +1,16 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CustodyLabels Component should render correctly 1`] = `
|
||||
<div>
|
||||
<label
|
||||
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
||||
for="address-index"
|
||||
>
|
||||
<p
|
||||
class="box mm-text custody-label mm-text--h9 mm-text--font-weight-normal mm-text--text-transform-uppercase box--margin-top-1 box--margin-right-1 box--margin-bottom-2 box--padding-top-1 box--padding-right-2 box--padding-bottom-1 box--padding-left-2 box--flex-direction-row box--color-text-muted box--background-color-background-alternative box--rounded-sm"
|
||||
>
|
||||
value
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
`;
|
58
ui/components/institutional/custody-labels/custody-labels.js
Normal file
58
ui/components/institutional/custody-labels/custody-labels.js
Normal file
@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Text, Label } from '../../component-library';
|
||||
import {
|
||||
TEXT_TRANSFORM,
|
||||
BackgroundColor,
|
||||
TextColor,
|
||||
FONT_WEIGHT,
|
||||
BorderRadius,
|
||||
TypographyVariant,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
|
||||
const CustodyLabels = (props) => {
|
||||
const { labels, index, background, hideNetwork } = props;
|
||||
const filteredLabels = hideNetwork
|
||||
? labels.filter((item) => item.key !== 'network_name')
|
||||
: labels;
|
||||
|
||||
return (
|
||||
<Label
|
||||
display={['flex']}
|
||||
flexDirection={['row']}
|
||||
htmlFor={`address-${index || 0}`}
|
||||
>
|
||||
{filteredLabels.map((item) => (
|
||||
<Text
|
||||
key={item.key}
|
||||
textTransform={TEXT_TRANSFORM.UPPERCASE}
|
||||
className="custody-label"
|
||||
style={background ? { background } : {}}
|
||||
marginTop={1}
|
||||
marginRight={1}
|
||||
marginBottom={2}
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
backgroundColor={BackgroundColor.backgroundAlternative}
|
||||
color={TextColor.textMuted}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
borderRadius={BorderRadius.SM}
|
||||
variant={TypographyVariant.H9}
|
||||
>
|
||||
{item.value}
|
||||
</Text>
|
||||
))}
|
||||
</Label>
|
||||
);
|
||||
};
|
||||
|
||||
CustodyLabels.propTypes = {
|
||||
labels: PropTypes.array,
|
||||
index: PropTypes.string,
|
||||
background: PropTypes.string,
|
||||
hideNetwork: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default CustodyLabels;
|
@ -0,0 +1,9 @@
|
||||
.custody-label {
|
||||
z-index: 1;
|
||||
letter-spacing: 0.5px;
|
||||
white-space: nowrap;
|
||||
max-width: 80px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import CustodyLabels from '.';
|
||||
|
||||
export default {
|
||||
title: 'Components/Institutional/CustodyLabels',
|
||||
component: CustodyLabels,
|
||||
args: {
|
||||
labels: [{ key: 'testKey', value: 'value' }],
|
||||
index: 'index',
|
||||
hideNetwork: 'true',
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <CustodyLabels {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'CustodyLabels';
|
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import CustodyLabels from './custody-labels';
|
||||
|
||||
describe('CustodyLabels Component', () => {
|
||||
it('should render correctly', () => {
|
||||
const props = {
|
||||
labels: [{ key: 'testKey', value: 'value' }],
|
||||
index: 'index',
|
||||
hideNetwork: 'true',
|
||||
};
|
||||
|
||||
const { container } = render(<CustodyLabels {...props} />);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
1
ui/components/institutional/custody-labels/index.js
Normal file
1
ui/components/institutional/custody-labels/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './custody-labels';
|
@ -297,6 +297,15 @@ export const BLOCK_SIZES = {
|
||||
FULL: 'full',
|
||||
};
|
||||
|
||||
export enum TextAlign {
|
||||
Left = 'left',
|
||||
Center = 'center',
|
||||
Right = 'right',
|
||||
Justify = 'justify',
|
||||
End = 'end',
|
||||
Start = 'start',
|
||||
}
|
||||
|
||||
export const TEXT_ALIGN = {
|
||||
LEFT: 'left',
|
||||
CENTER: 'center',
|
||||
@ -306,24 +315,50 @@ export const TEXT_ALIGN = {
|
||||
START: 'start',
|
||||
};
|
||||
|
||||
export enum TextTransform {
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
Uppercase = 'uppercase',
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
Lowercase = 'lowercase',
|
||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||
Capitalize = 'capitalize',
|
||||
}
|
||||
|
||||
export const TEXT_TRANSFORM = {
|
||||
UPPERCASE: 'uppercase',
|
||||
LOWERCASE: 'lowercase',
|
||||
CAPITALIZE: 'capitalize',
|
||||
};
|
||||
|
||||
export enum FontWeight {
|
||||
Bold = 'bold',
|
||||
Medium = 'medium',
|
||||
Normal = 'normal',
|
||||
}
|
||||
|
||||
export const FONT_WEIGHT = {
|
||||
BOLD: 'bold',
|
||||
MEDIUM: 'medium',
|
||||
NORMAL: 'normal',
|
||||
};
|
||||
|
||||
export enum OverflowWrap {
|
||||
BreakWord = 'break-word',
|
||||
Anywhere = 'anywhere',
|
||||
Normal = 'normal',
|
||||
}
|
||||
|
||||
export const OVERFLOW_WRAP = {
|
||||
BREAK_WORD: 'break-word',
|
||||
ANYWHERE: 'anywhere',
|
||||
NORMAL: 'normal',
|
||||
};
|
||||
|
||||
export enum FontStyle {
|
||||
Italic = 'italic',
|
||||
Normal = 'normal',
|
||||
}
|
||||
|
||||
export const FONT_STYLE = {
|
||||
ITALIC: 'italic',
|
||||
NORMAL: 'normal',
|
||||
|
@ -71,12 +71,6 @@ export const PERMISSION_DESCRIPTIONS = deepFreeze({
|
||||
weight: 2,
|
||||
}),
|
||||
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
||||
[RestrictedMethods.snap_confirm]: ({ t }) => ({
|
||||
label: t('permission_customConfirmation'),
|
||||
description: t('permission_customConfirmationDescription'),
|
||||
leftIcon: ICON_NAMES.SECURITY_TICK,
|
||||
weight: 3,
|
||||
}),
|
||||
[RestrictedMethods.snap_dialog]: ({ t }) => ({
|
||||
label: t('permission_dialog'),
|
||||
description: t('permission_dialogDescription'),
|
||||
|
40
ui/hooks/useDraftTransactionGasValues.js
Normal file
40
ui/hooks/useDraftTransactionGasValues.js
Normal file
@ -0,0 +1,40 @@
|
||||
import { useSelector } from 'react-redux';
|
||||
import { getCurrentDraftTransaction } from '../ducks/send';
|
||||
import { getUnapprovedTransactions } from '../selectors';
|
||||
|
||||
/**
|
||||
* Returns an object that resembles the txData.txParams from the Transactions state.
|
||||
* While processing gas details for send transaction and edit transaction,
|
||||
* the gas data from draftTransaction and unapprovedTx has to be reorganized
|
||||
* to mimic the txdata.txParam from a confirmTransaction
|
||||
*
|
||||
* @returns {Object txData.txParams}
|
||||
*/
|
||||
export const useDraftTransactionGasValues = () => {
|
||||
const draftTransaction = useSelector(getCurrentDraftTransaction);
|
||||
const unapprovedTxs = useSelector(getUnapprovedTransactions);
|
||||
|
||||
let transactionData = {};
|
||||
if (Object.keys(draftTransaction).length !== 0) {
|
||||
const editingTransaction = unapprovedTxs[draftTransaction.id];
|
||||
transactionData = {
|
||||
txParams: {
|
||||
gasPrice: draftTransaction.gas?.gasPrice,
|
||||
gas: editingTransaction?.userEditedGasLimit
|
||||
? editingTransaction?.txParams?.gas
|
||||
: draftTransaction.gas?.gasLimit,
|
||||
maxFeePerGas: editingTransaction?.txParams?.maxFeePerGas
|
||||
? editingTransaction?.txParams?.maxFeePerGas
|
||||
: draftTransaction.gas?.maxFeePerGas,
|
||||
maxPriorityFeePerGas: editingTransaction?.txParams?.maxPriorityFeePerGas
|
||||
? editingTransaction?.txParams?.maxPriorityFeePerGas
|
||||
: draftTransaction.gas?.maxPriorityFeePerGas,
|
||||
value: draftTransaction.amount?.value,
|
||||
type: draftTransaction.transactionType,
|
||||
},
|
||||
userFeeLevel: editingTransaction?.userFeeLevel,
|
||||
};
|
||||
}
|
||||
|
||||
return { transactionData };
|
||||
};
|
@ -23,7 +23,6 @@ import {
|
||||
AlignItems,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { ConfirmPageContainerWarning } from '../../../components/app/confirm-page-container/confirm-page-container-content';
|
||||
import GasDetailsItem from '../../../components/app/gas-details-item';
|
||||
import LedgerInstructionField from '../../../components/app/ledger-instruction-field';
|
||||
import { TokenStandard } from '../../../../shared/constants/transaction';
|
||||
import { CHAIN_IDS, TEST_CHAINS } from '../../../../shared/constants/network';
|
||||
@ -34,6 +33,7 @@ import {
|
||||
} from '../../../components/component-library/icon/deprecated';
|
||||
import { ButtonIcon } from '../../../components/component-library/button-icon/deprecated';
|
||||
import { Text } from '../../../components/component-library';
|
||||
import { ConfirmGasDisplay } from '../../../components/app/confirm-gas-display';
|
||||
|
||||
export default class ConfirmApproveContent extends Component {
|
||||
static contextTypes = {
|
||||
@ -170,7 +170,7 @@ export default class ConfirmApproveContent extends Component {
|
||||
!renderSimulationFailureWarning
|
||||
) {
|
||||
return (
|
||||
<GasDetailsItem
|
||||
<ConfirmGasDisplay
|
||||
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||
/>
|
||||
);
|
||||
|
@ -374,9 +374,7 @@ exports[`Confirm Transaction Base should match snapshot 1`] = `
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
<div
|
||||
class="confirm-page-container-content__currency-container"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0.000021"
|
||||
@ -397,18 +395,14 @@ exports[`Confirm Transaction Base should match snapshot 1`] = `
|
||||
<div
|
||||
class="transaction-detail-item__row"
|
||||
>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
<div />
|
||||
<h6
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
|
||||
>
|
||||
<strong>
|
||||
Max fee:
|
||||
</strong>
|
||||
<div
|
||||
class="confirm-page-container-content__currency-container"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
title="0.000021"
|
||||
|
@ -28,18 +28,9 @@ import {
|
||||
import { TransactionModalContextProvider } from '../../contexts/transaction-modal';
|
||||
import TransactionDetail from '../../components/app/transaction-detail/transaction-detail.component';
|
||||
import TransactionDetailItem from '../../components/app/transaction-detail-item/transaction-detail-item.component';
|
||||
import InfoTooltip from '../../components/ui/info-tooltip/info-tooltip';
|
||||
import LoadingHeartBeat from '../../components/ui/loading-heartbeat';
|
||||
import GasDetailsItem from '../../components/app/gas-details-item';
|
||||
import GasTiming from '../../components/app/gas-timing/gas-timing.component';
|
||||
import LedgerInstructionField from '../../components/app/ledger-instruction-field';
|
||||
import MultiLayerFeeMessage from '../../components/app/multilayer-fee-message';
|
||||
import Typography from '../../components/ui/typography/typography';
|
||||
import {
|
||||
TextColor,
|
||||
FONT_STYLE,
|
||||
TypographyVariant,
|
||||
} from '../../helpers/constants/design-system';
|
||||
import {
|
||||
disconnectGasFeeEstimatePoller,
|
||||
getGasFeeEstimatesAndStartPolling,
|
||||
@ -53,16 +44,13 @@ import { NETWORK_TO_NAME_MAP } from '../../../shared/constants/network';
|
||||
import {
|
||||
addHexes,
|
||||
hexToDecimal,
|
||||
hexWEIToDecGWEI,
|
||||
} from '../../../shared/modules/conversion.utils';
|
||||
import TransactionAlerts from '../../components/app/transaction-alerts';
|
||||
import { ConfirmHexData } from '../../components/app/confirm-hexdata';
|
||||
import { ConfirmData } from '../../components/app/confirm-data';
|
||||
import { ConfirmTitle } from '../../components/app/confirm-title';
|
||||
import { ConfirmSubTitle } from '../../components/app/confirm-subtitle';
|
||||
|
||||
const renderHeartBeatIfNotInTest = () =>
|
||||
process.env.IN_TEST ? null : <LoadingHeartBeat />;
|
||||
import { ConfirmGasDisplay } from '../../components/app/confirm-gas-display';
|
||||
|
||||
export default class ConfirmTransactionBase extends Component {
|
||||
static contextTypes = {
|
||||
@ -136,7 +124,6 @@ export default class ConfirmTransactionBase extends Component {
|
||||
maxFeePerGas: PropTypes.string,
|
||||
maxPriorityFeePerGas: PropTypes.string,
|
||||
baseFeePerGas: PropTypes.string,
|
||||
isMainnet: PropTypes.bool,
|
||||
gasFeeIsCustom: PropTypes.bool,
|
||||
showLedgerSteps: PropTypes.bool.isRequired,
|
||||
nativeCurrency: PropTypes.string,
|
||||
@ -319,11 +306,7 @@ export default class ConfirmTransactionBase extends Component {
|
||||
txData,
|
||||
useNativeCurrencyAsPrimaryCurrency,
|
||||
primaryTotalTextOverrideMaxAmount,
|
||||
maxFeePerGas,
|
||||
maxPriorityFeePerGas,
|
||||
isMainnet,
|
||||
showLedgerSteps,
|
||||
supportsEIP1559,
|
||||
isMultiLayerFeeNetwork,
|
||||
nativeCurrency,
|
||||
isBuyableChain,
|
||||
@ -439,128 +422,6 @@ export default class ConfirmTransactionBase extends Component {
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
const renderGasDetailsItem = () => {
|
||||
return this.supportsEIP1559 ? (
|
||||
<GasDetailsItem
|
||||
key="gas_details"
|
||||
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||
/>
|
||||
) : (
|
||||
<TransactionDetailItem
|
||||
key="gas-item"
|
||||
detailTitle={
|
||||
txData.dappSuggestedGasFees ? (
|
||||
<>
|
||||
{t('transactionDetailGasHeading')}
|
||||
<InfoTooltip
|
||||
contentText={t('transactionDetailDappGasTooltip')}
|
||||
position="top"
|
||||
>
|
||||
<i className="fa fa-info-circle" />
|
||||
</InfoTooltip>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{t('transactionDetailGasHeading')}
|
||||
<InfoTooltip
|
||||
contentText={
|
||||
<>
|
||||
<p>
|
||||
{t('transactionDetailGasTooltipIntro', [
|
||||
isMainnet ? t('networkNameEthereum') : '',
|
||||
])}
|
||||
</p>
|
||||
<p>{t('transactionDetailGasTooltipExplanation')}</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('transactionDetailGasTooltipConversion')}
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
}
|
||||
position="top"
|
||||
>
|
||||
<i className="fa fa-info-circle" />
|
||||
</InfoTooltip>
|
||||
</>
|
||||
)
|
||||
}
|
||||
detailText={
|
||||
useCurrencyRateCheck && (
|
||||
<div className="confirm-page-container-content__currency-container test">
|
||||
{renderHeartBeatIfNotInTest()}
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={SECONDARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
detailTotal={
|
||||
<div className="confirm-page-container-content__currency-container">
|
||||
{renderHeartBeatIfNotInTest()}
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
numberOfDecimals={6}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
subText={
|
||||
<>
|
||||
<strong key="editGasSubTextFeeLabel">
|
||||
{t('editGasSubTextFeeLabel')}
|
||||
</strong>
|
||||
<div
|
||||
key="editGasSubTextFeeValue"
|
||||
className="confirm-page-container-content__currency-container"
|
||||
>
|
||||
{renderHeartBeatIfNotInTest()}
|
||||
<UserPreferencedCurrencyDisplay
|
||||
key="editGasSubTextFeeAmount"
|
||||
type={PRIMARY}
|
||||
value={hexMaximumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
subTitle={
|
||||
<>
|
||||
{txData.dappSuggestedGasFees ? (
|
||||
<Typography
|
||||
variant={TypographyVariant.H7}
|
||||
fontStyle={FONT_STYLE.ITALIC}
|
||||
color={TextColor.textAlternative}
|
||||
>
|
||||
{t('transactionDetailDappGasMoreInfo')}
|
||||
</Typography>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{supportsEIP1559 && (
|
||||
<GasTiming
|
||||
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
||||
maxPriorityFeePerGas ||
|
||||
txData.txParams.maxPriorityFeePerGas,
|
||||
).toString()}
|
||||
maxFeePerGas={hexWEIToDecGWEI(
|
||||
maxFeePerGas || txData.txParams.maxFeePerGas,
|
||||
).toString()}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const simulationFailureWarning = () => (
|
||||
<div className="confirm-page-container-content__error-container">
|
||||
<SimulationErrorMessage
|
||||
@ -594,9 +455,11 @@ export default class ConfirmTransactionBase extends Component {
|
||||
}
|
||||
rows={[
|
||||
renderSimulationFailureWarning && simulationFailureWarning(),
|
||||
!renderSimulationFailureWarning &&
|
||||
!isMultiLayerFeeNetwork &&
|
||||
renderGasDetailsItem(),
|
||||
!renderSimulationFailureWarning && !isMultiLayerFeeNetwork && (
|
||||
<ConfirmGasDisplay
|
||||
userAcknowledgedGasMissing={userAcknowledgedGasMissing}
|
||||
/>
|
||||
),
|
||||
!renderSimulationFailureWarning && isMultiLayerFeeNetwork && (
|
||||
<MultiLayerFeeMessage
|
||||
transaction={txData}
|
||||
|
@ -23,7 +23,11 @@ setBackgroundConnection({
|
||||
});
|
||||
|
||||
const baseStore = {
|
||||
send: INITIAL_SEND_STATE_FOR_EXISTING_DRAFT,
|
||||
send: {
|
||||
...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT,
|
||||
currentTransactionUUID: null,
|
||||
draftTransactions: {},
|
||||
},
|
||||
DNS: domainInitialState,
|
||||
gas: {
|
||||
customData: { limit: null, price: null },
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { TypographyVariant } from '../../../../../helpers/constants/design-system';
|
||||
import { mapToTemplate } from '../../../../../components/app/flask/snap-ui-renderer';
|
||||
import { DelineatorType } from '../../../../../helpers/constants/flask';
|
||||
|
||||
function getValues(pendingApproval, t, actions) {
|
||||
const {
|
||||
snapName,
|
||||
requestData: { content, title, description, textAreaContent },
|
||||
requestData: { content },
|
||||
} = pendingApproval;
|
||||
|
||||
return {
|
||||
@ -25,49 +24,7 @@ function getValues(pendingApproval, t, actions) {
|
||||
snapName,
|
||||
},
|
||||
// TODO: Replace with SnapUIRenderer when we don't need to inject the input manually.
|
||||
// TODO: Remove ternary once snap_confirm has been removed.
|
||||
children: content
|
||||
? mapToTemplate(content)
|
||||
: [
|
||||
{
|
||||
element: 'Typography',
|
||||
key: 'title',
|
||||
children: title,
|
||||
props: {
|
||||
variant: TypographyVariant.H3,
|
||||
fontWeight: 'bold',
|
||||
boxProps: {
|
||||
marginBottom: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
...(description
|
||||
? [
|
||||
{
|
||||
element: 'Typography',
|
||||
key: 'subtitle',
|
||||
children: description,
|
||||
props: {
|
||||
variant: TypographyVariant.H6,
|
||||
boxProps: {
|
||||
marginBottom: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(textAreaContent
|
||||
? [
|
||||
{
|
||||
element: 'Copyable',
|
||||
key: 'snap-dialog-content-text',
|
||||
props: {
|
||||
text: textAreaContent,
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
children: mapToTemplate(content),
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -0,0 +1,188 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`CustodyAccountList renders accounts 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="box box--padding-top-4 box--padding-right-7 box--padding-bottom-7 box--padding-left-7 box--flex-direction-row"
|
||||
>
|
||||
<div
|
||||
class="box custody-account-list box--display-flex box--flex-direction-column box--width-full"
|
||||
data-testid="custody-account-list"
|
||||
>
|
||||
<div
|
||||
class="box custody-account-list__item box--display-flex box--flex-direction-row"
|
||||
>
|
||||
<div
|
||||
class="box box--display-flex box--flex-direction-row box--align-items-flex-start"
|
||||
data-testid="custody-account-list-item-radio-button"
|
||||
>
|
||||
<input
|
||||
id="address-0"
|
||||
name="selectedAccount"
|
||||
type="checkbox"
|
||||
value="0x1234567890123456789012345678901234567890"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
|
||||
>
|
||||
<label
|
||||
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default"
|
||||
for="address-0"
|
||||
>
|
||||
<span
|
||||
class="box mm-text custody-account-list__item__name mm-text--inherit box--padding-right-1 box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
Test Account 1
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-right-3 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
||||
for="address-0"
|
||||
>
|
||||
<span
|
||||
class="box mm-text custody-account-list__item mm-text--body-md box--display-flex box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
<a
|
||||
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
|
||||
href="https://etherscan.io/address/0x1234567890123456789012345678901234567890"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<span
|
||||
class="box mm-text mm-text--inherit box--flex-direction-row box--color-primary-default"
|
||||
>
|
||||
0x123...7890
|
||||
<span
|
||||
class="box mm-icon mm-icon--size-md box--margin-left-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
|
||||
style="mask-image: url('./images/icons/undefined.svg');"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
<div>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-1"
|
||||
class=""
|
||||
data-original-title="[copyToClipboard]"
|
||||
data-tooltipped=""
|
||||
style="display: inline; background-color: transparent;"
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
class="custody-account-list__item__clipboard"
|
||||
>
|
||||
<span
|
||||
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-muted"
|
||||
style="mask-image: url('./images/icons/undefined.svg');"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="box box--display-flex box--flex-direction-row box--justify-content-space-between"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="box custody-account-list__item box--display-flex box--flex-direction-row"
|
||||
>
|
||||
<div
|
||||
class="box box--display-flex box--flex-direction-row box--align-items-flex-start"
|
||||
data-testid="custody-account-list-item-radio-button"
|
||||
>
|
||||
<input
|
||||
id="address-1"
|
||||
name="selectedAccount"
|
||||
type="checkbox"
|
||||
value="0x0987654321098765432109876543210987654321"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="box box--margin-left-2 box--display-flex box--flex-direction-column box--width-full"
|
||||
>
|
||||
<label
|
||||
class="box mm-text mm-label mm-label--html-for custody-account-list__item__title mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-left-2 box--display-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default"
|
||||
for="address-1"
|
||||
>
|
||||
<span
|
||||
class="box mm-text custody-account-list__item__name mm-text--inherit box--padding-right-1 box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
Test Account 2
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="box mm-text mm-label mm-label--html-for mm-text--body-md mm-text--font-weight-bold box--margin-top-2 box--margin-right-3 box--display-flex box--flex-direction-row box--align-items-center box--color-text-default"
|
||||
for="address-1"
|
||||
>
|
||||
<span
|
||||
class="box mm-text custody-account-list__item mm-text--body-md box--display-flex box--flex-direction-row box--color-text-default"
|
||||
>
|
||||
<a
|
||||
class="box mm-text mm-button-base mm-button-link mm-button-link--size-auto mm-text--body-md box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-primary-default box--background-color-transparent"
|
||||
href="https://etherscan.io/address/0x0987654321098765432109876543210987654321"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<span
|
||||
class="box mm-text mm-text--inherit box--flex-direction-row box--color-primary-default"
|
||||
>
|
||||
0x098...4321
|
||||
<span
|
||||
class="box mm-icon mm-icon--size-md box--margin-left-1 box--display-inline-block box--flex-direction-row box--color-primary-default"
|
||||
style="mask-image: url('./images/icons/undefined.svg');"
|
||||
/>
|
||||
</span>
|
||||
</a>
|
||||
<div>
|
||||
<div
|
||||
aria-describedby="tippy-tooltip-2"
|
||||
class=""
|
||||
data-original-title="[copyToClipboard]"
|
||||
data-tooltipped=""
|
||||
style="display: inline; background-color: transparent;"
|
||||
tabindex="0"
|
||||
>
|
||||
<button
|
||||
class="custody-account-list__item__clipboard"
|
||||
>
|
||||
<span
|
||||
class="box mm-icon mm-icon--size-md box--display-inline-block box--flex-direction-row box--color-icon-muted"
|
||||
style="mask-image: url('./images/icons/undefined.svg');"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
class="box box--display-flex box--flex-direction-row box--justify-content-space-between"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="box custody-account-list__buttons box--padding-top-5 box--padding-right-7 box--padding-bottom-7 box--padding-left-7 box--display-flex box--flex-direction-row box--justify-content-space-between box--width-full"
|
||||
>
|
||||
<button
|
||||
class="button btn--rounded btn-default btn--large custody-account-list__button"
|
||||
data-testid="custody-account-cancel-button"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
[cancel]
|
||||
</button>
|
||||
<button
|
||||
class="button btn--rounded btn-primary btn--large custody-account-list__button"
|
||||
data-testid="custody-account-connect-button"
|
||||
disabled=""
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
[connect]
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,218 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '../../../../components/ui/button';
|
||||
import CustodyLabels from '../../../../components/institutional/custody-labels';
|
||||
import { SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP } from '../../../../../shared/constants/swaps';
|
||||
import { CHAIN_IDS } from '../../../../../shared/constants/network';
|
||||
import { shortenAddress } from '../../../../helpers/utils/util';
|
||||
import Tooltip from '../../../../components/ui/tooltip';
|
||||
import {
|
||||
TextVariant,
|
||||
JustifyContent,
|
||||
BLOCK_SIZES,
|
||||
DISPLAY,
|
||||
IconColor,
|
||||
} from '../../../../helpers/constants/design-system';
|
||||
import { useI18nContext } from '../../../../hooks/useI18nContext';
|
||||
import Box from '../../../../components/ui/box';
|
||||
import {
|
||||
Text,
|
||||
Label,
|
||||
Icon,
|
||||
IconName,
|
||||
IconSize,
|
||||
ButtonLink,
|
||||
} from '../../../../components/component-library';
|
||||
import { useCopyToClipboard } from '../../../../hooks/useCopyToClipboard';
|
||||
|
||||
const getButtonLinkHref = (account) => {
|
||||
const url = SWAPS_CHAINID_DEFAULT_BLOCK_EXPLORER_URL_MAP[CHAIN_IDS.MAINNET];
|
||||
return `${url}address/${account.address}`;
|
||||
};
|
||||
|
||||
export default function CustodyAccountList({
|
||||
rawList,
|
||||
accounts,
|
||||
onAccountChange,
|
||||
selectedAccounts,
|
||||
onCancel,
|
||||
onAddAccounts,
|
||||
custody,
|
||||
}) {
|
||||
const t = useI18nContext();
|
||||
const [copied, handleCopy] = useCopyToClipboard();
|
||||
const tooltipText = copied ? t('copiedExclamation') : t('copyToClipboard');
|
||||
const disabled = Object.keys(selectedAccounts).length === 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box paddingTop={4} paddingRight={7} paddingBottom={7} paddingLeft={7}>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={['column']}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
className="custody-account-list"
|
||||
data-testid="custody-account-list"
|
||||
>
|
||||
{accounts.map((account, idx) => (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
className="custody-account-list__item"
|
||||
key={account.address}
|
||||
>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
alignItems={['flex-start']}
|
||||
data-testid="custody-account-list-item-radio-button"
|
||||
>
|
||||
{!rawList && (
|
||||
<input
|
||||
type="checkbox"
|
||||
name="selectedAccount"
|
||||
id={`address-${idx}`}
|
||||
value={account.address}
|
||||
onChange={(e) =>
|
||||
onAccountChange({
|
||||
name: account.name,
|
||||
address: e.target.value,
|
||||
custodianDetails: account.custodianDetails,
|
||||
labels: account.labels,
|
||||
chainId: account.chainId,
|
||||
})
|
||||
}
|
||||
checked={
|
||||
selectedAccounts && selectedAccounts[account.address]
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
flexDirection={['column']}
|
||||
marginLeft={2}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
>
|
||||
<Label
|
||||
display={DISPLAY.FLEX}
|
||||
justifyContent={JustifyContent.center}
|
||||
marginTop={2}
|
||||
marginLeft={2}
|
||||
htmlFor={`address-${idx}`}
|
||||
className="custody-account-list__item__title"
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
variant={TextVariant.inherit}
|
||||
size={TextVariant.bodySm}
|
||||
paddingRight={1}
|
||||
className="custody-account-list__item__name"
|
||||
>
|
||||
{account.name}
|
||||
</Text>
|
||||
</Label>
|
||||
<Label
|
||||
display={DISPLAY.FLEX}
|
||||
size={TextVariant.bodySm}
|
||||
marginTop={2}
|
||||
marginRight={3}
|
||||
htmlFor={`address-${idx}`}
|
||||
>
|
||||
<Text
|
||||
as="span"
|
||||
variant={TextVariant.bodyMd}
|
||||
display={DISPLAY.FLEX}
|
||||
className="custody-account-list__item"
|
||||
>
|
||||
<ButtonLink
|
||||
href={getButtonLinkHref(account)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{shortenAddress(account.address)}
|
||||
<Icon
|
||||
name={IconSize.EXPORT}
|
||||
size={IconName.SM}
|
||||
color={IconColor.primaryDefault}
|
||||
marginLeft={1}
|
||||
/>
|
||||
</ButtonLink>
|
||||
<Tooltip
|
||||
position="bottom"
|
||||
title={tooltipText}
|
||||
style={{ backgroundColor: 'transparent' }}
|
||||
>
|
||||
<button
|
||||
className="custody-account-list__item__clipboard"
|
||||
onClick={() => handleCopy(account.address)}
|
||||
>
|
||||
<Icon
|
||||
name={IconSize.COPY}
|
||||
size={IconName.XS}
|
||||
color={IconColor.iconMuted}
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</Text>
|
||||
</Label>
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
>
|
||||
{account.labels && (
|
||||
<CustodyLabels
|
||||
labels={account.labels}
|
||||
index={idx.toString()}
|
||||
hideNetwork
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
{!rawList && (
|
||||
<Box
|
||||
display={DISPLAY.FLEX}
|
||||
width={BLOCK_SIZES.FULL}
|
||||
justifyContent={JustifyContent.spaceBetween}
|
||||
paddingTop={5}
|
||||
paddingRight={7}
|
||||
paddingBottom={7}
|
||||
paddingLeft={7}
|
||||
className="custody-account-list__buttons"
|
||||
>
|
||||
<Button
|
||||
data-testid="custody-account-cancel-button"
|
||||
type="default"
|
||||
large
|
||||
className="custody-account-list__button"
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t('cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="custody-account-connect-button"
|
||||
type="primary"
|
||||
large
|
||||
className="custody-account-list__button"
|
||||
disabled={disabled}
|
||||
onClick={() => onAddAccounts(custody)}
|
||||
>
|
||||
{t('connect')}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
CustodyAccountList.propTypes = {
|
||||
custody: PropTypes.string,
|
||||
accounts: PropTypes.array.isRequired,
|
||||
onAccountChange: PropTypes.func,
|
||||
selectedAccounts: PropTypes.object,
|
||||
onAddAccounts: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
rawList: PropTypes.bool,
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import CustodyAccountList from '.';
|
||||
|
||||
const testAccounts = [
|
||||
{
|
||||
address: '0x1234567890123456789012345678901234567890',
|
||||
name: 'Test Account 1',
|
||||
chainId: 1,
|
||||
},
|
||||
{
|
||||
address: '0x0987654321098765432109876543210987654321',
|
||||
name: 'Test Account 2',
|
||||
chainId: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
title: 'Pages/Institutional/CustodyAccountList',
|
||||
component: CustodyAccountList,
|
||||
args: {
|
||||
custody: 'Test',
|
||||
accounts: testAccounts,
|
||||
onAccountChange: () => undefined,
|
||||
selectedAccounts: {},
|
||||
onAddAccounts: () => undefined,
|
||||
onCancel: () => undefined,
|
||||
provider: 'Test',
|
||||
rawList: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const DefaultStory = (args) => <CustodyAccountList {...args} />;
|
||||
|
||||
DefaultStory.storyName = 'CustodyAccountList';
|
@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import CustodyAccountList from './account-list';
|
||||
|
||||
const testAccounts = [
|
||||
{
|
||||
address: '0x1234567890123456789012345678901234567890',
|
||||
name: 'Test Account 1',
|
||||
chainId: 1,
|
||||
},
|
||||
{
|
||||
address: '0x0987654321098765432109876543210987654321',
|
||||
name: 'Test Account 2',
|
||||
chainId: 1,
|
||||
},
|
||||
];
|
||||
|
||||
describe('CustodyAccountList', () => {
|
||||
const onAccountChangeMock = jest.fn();
|
||||
const onCancelMock = jest.fn();
|
||||
const onAddAccountsMock = jest.fn();
|
||||
const selectedAccountsMock = {};
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders accounts', () => {
|
||||
const { container } = render(
|
||||
<CustodyAccountList
|
||||
accounts={testAccounts}
|
||||
selectedAccounts={selectedAccountsMock}
|
||||
onAccountChange={onAccountChangeMock}
|
||||
onCancel={onCancelMock}
|
||||
onAddAccounts={onAddAccountsMock}
|
||||
custody="Test"
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('calls onAccountChange when an account is selected', () => {
|
||||
render(
|
||||
<CustodyAccountList
|
||||
accounts={testAccounts}
|
||||
selectedAccounts={selectedAccountsMock}
|
||||
onAccountChange={onAccountChangeMock}
|
||||
onCancel={onCancelMock}
|
||||
onAddAccounts={onAddAccountsMock}
|
||||
custody="Test"
|
||||
/>,
|
||||
);
|
||||
|
||||
const firstAccountCheckbox = screen.getAllByRole('checkbox')[0];
|
||||
fireEvent.click(firstAccountCheckbox);
|
||||
|
||||
expect(onAccountChangeMock).toHaveBeenCalledTimes(1);
|
||||
expect(onAccountChangeMock).toHaveBeenCalledWith({
|
||||
name: 'Test Account 1',
|
||||
address: '0x1234567890123456789012345678901234567890',
|
||||
custodianDetails: undefined,
|
||||
labels: undefined,
|
||||
chainId: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('calls onCancel when the Cancel button is clicked', () => {
|
||||
render(
|
||||
<CustodyAccountList
|
||||
accounts={testAccounts}
|
||||
selectedAccounts={selectedAccountsMock}
|
||||
onAccountChange={onAccountChangeMock}
|
||||
onCancel={onCancelMock}
|
||||
onAddAccounts={onAddAccountsMock}
|
||||
custody="Test"
|
||||
/>,
|
||||
);
|
||||
|
||||
const cancelButton = screen.getByTestId('custody-account-cancel-button');
|
||||
fireEvent.click(cancelButton);
|
||||
|
||||
expect(onCancelMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls onAddAccounts when the Connect button is clicked', () => {
|
||||
selectedAccountsMock['0x1234567890123456789012345678901234567890'] = true;
|
||||
selectedAccountsMock['0x0987654321098765432109876543210987654321'] = true;
|
||||
|
||||
render(
|
||||
<CustodyAccountList
|
||||
accounts={testAccounts}
|
||||
selectedAccounts={selectedAccountsMock}
|
||||
onAccountChange={onAccountChangeMock}
|
||||
onCancel={onCancelMock}
|
||||
onAddAccounts={onAddAccountsMock}
|
||||
custody="Test"
|
||||
/>,
|
||||
);
|
||||
|
||||
const addAccountsButton = screen.getByTestId(
|
||||
'custody-account-connect-button',
|
||||
);
|
||||
fireEvent.click(addAccountsButton);
|
||||
|
||||
expect(onAddAccountsMock).toHaveBeenCalledTimes(1);
|
||||
expect(onAddAccountsMock).toHaveBeenCalledWith('Test');
|
||||
});
|
||||
});
|
@ -0,0 +1 @@
|
||||
export { default } from './account-list';
|
@ -0,0 +1,49 @@
|
||||
.custody-account-list {
|
||||
flex: 1;
|
||||
max-height: 50vh;
|
||||
overflow: auto;
|
||||
|
||||
&__item {
|
||||
border-bottom: 1px solid #d2d8dd;
|
||||
|
||||
input {
|
||||
margin: 12px 0 0 10px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
flex-flow: row;
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
max-width: 200px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__clipboard {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&__item:first-child {
|
||||
border-top: 1px solid #d2d8dd;
|
||||
}
|
||||
|
||||
&__item:last-child {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&__item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
border-top: 1px solid #d2d8dd;
|
||||
}
|
||||
|
||||
&__button:not(:last-child) {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
@ -304,6 +304,7 @@ export default class Home extends PureComponent {
|
||||
iconName={ICON_NAMES.CLOSE}
|
||||
size={ICON_SIZES.SM}
|
||||
ariaLabel={t('close')}
|
||||
onClick={onAutoHide}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
|
@ -12,6 +12,9 @@
|
||||
@import 'connected-accounts/index';
|
||||
@import 'connected-sites/index';
|
||||
@import 'create-account/index';
|
||||
///: BEGIN:ONLY_INCLUDE_IN(mmi)
|
||||
@import "create-account/institutional/connect-custody/index";
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
@import 'error/index';
|
||||
@import 'send/gas-display/index';
|
||||
@import 'home/index';
|
||||
|
@ -1,13 +1,10 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { I18nContext } from '../../../contexts/i18n';
|
||||
import { useGasFeeContext } from '../../../contexts/gasFee';
|
||||
import { PRIMARY, SECONDARY } from '../../../helpers/constants/common';
|
||||
import UserPreferencedCurrencyDisplay from '../../../components/app/user-preferenced-currency-display';
|
||||
import GasTiming from '../../../components/app/gas-timing';
|
||||
import InfoTooltip from '../../../components/ui/info-tooltip';
|
||||
import Typography from '../../../components/ui/typography';
|
||||
import Button from '../../../components/ui/button';
|
||||
import Box from '../../../components/ui/box';
|
||||
@ -16,13 +13,11 @@ import {
|
||||
DISPLAY,
|
||||
FLEX_DIRECTION,
|
||||
BLOCK_SIZES,
|
||||
Color,
|
||||
FONT_STYLE,
|
||||
FONT_WEIGHT,
|
||||
} from '../../../helpers/constants/design-system';
|
||||
import { TokenStandard } from '../../../../shared/constants/transaction';
|
||||
import LoadingHeartBeat from '../../../components/ui/loading-heartbeat';
|
||||
import TransactionDetailItem from '../../../components/app/transaction-detail-item';
|
||||
import { ConfirmGasDisplay } from '../../../components/app/confirm-gas-display';
|
||||
import { NETWORK_TO_NAME_MAP } from '../../../../shared/constants/network';
|
||||
import TransactionDetail from '../../../components/app/transaction-detail';
|
||||
import ActionableMessage from '../../../components/ui/actionable-message';
|
||||
@ -31,7 +26,6 @@ import {
|
||||
getPreferences,
|
||||
getIsBuyableChain,
|
||||
transactionFeeSelector,
|
||||
getIsMainnet,
|
||||
getIsTestnet,
|
||||
getUseCurrencyRateCheck,
|
||||
} from '../../../selectors';
|
||||
@ -43,7 +37,6 @@ import { showModal } from '../../../store/actions';
|
||||
import {
|
||||
addHexes,
|
||||
hexWEIToDecETH,
|
||||
hexWEIToDecGWEI,
|
||||
} from '../../../../shared/modules/conversion.utils';
|
||||
import {
|
||||
MetaMetricsEventCategory,
|
||||
@ -61,7 +54,6 @@ export default function GasDisplay({ gasError }) {
|
||||
const { openBuyCryptoInPdapp } = useRamps();
|
||||
|
||||
const currentProvider = useSelector(getProvider);
|
||||
const isMainnet = useSelector(getIsMainnet);
|
||||
const isTestnet = useSelector(getIsTestnet);
|
||||
const isBuyableChain = useSelector(getIsBuyableChain);
|
||||
const draftTransaction = useSelector(getCurrentDraftTransaction);
|
||||
@ -95,11 +87,9 @@ export default function GasDisplay({ gasError }) {
|
||||
userFeeLevel: editingTransaction?.userFeeLevel,
|
||||
};
|
||||
|
||||
const {
|
||||
hexMinimumTransactionFee,
|
||||
hexMaximumTransactionFee,
|
||||
hexTransactionTotal,
|
||||
} = useSelector((state) => transactionFeeSelector(state, transactionData));
|
||||
const { hexMaximumTransactionFee, hexTransactionTotal } = useSelector(
|
||||
(state) => transactionFeeSelector(state, transactionData),
|
||||
);
|
||||
|
||||
let title;
|
||||
if (
|
||||
@ -158,119 +148,13 @@ export default function GasDisplay({ gasError }) {
|
||||
detailTotal = primaryTotalTextOverrideMaxAmount;
|
||||
maxAmount = primaryTotalTextOverrideMaxAmount;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box className="gas-display">
|
||||
<TransactionDetail
|
||||
userAcknowledgedGasMissing={false}
|
||||
rows={[
|
||||
<TransactionDetailItem
|
||||
key="gas-item"
|
||||
detailTitle={
|
||||
<Box display={DISPLAY.FLEX}>
|
||||
<Box marginRight={1}>{t('gas')}</Box>
|
||||
<Typography
|
||||
as="span"
|
||||
marginTop={0}
|
||||
color={Color.textMuted}
|
||||
fontStyle={FONT_STYLE.ITALIC}
|
||||
fontWeight={FONT_WEIGHT.NORMAL}
|
||||
className="gas-display__title__estimate"
|
||||
>
|
||||
({t('transactionDetailGasInfoV2')})
|
||||
</Typography>
|
||||
<InfoTooltip
|
||||
contentText={
|
||||
<>
|
||||
<Typography variant={TypographyVariant.H7}>
|
||||
{t('transactionDetailGasTooltipIntro', [
|
||||
isMainnet ? t('networkNameEthereum') : '',
|
||||
])}
|
||||
</Typography>
|
||||
<Typography variant={TypographyVariant.H7}>
|
||||
{t('transactionDetailGasTooltipExplanation')}
|
||||
</Typography>
|
||||
<Typography variant={TypographyVariant.H7}>
|
||||
<a
|
||||
href="https://community.metamask.io/t/what-is-gas-why-do-transactions-take-so-long/3172"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('transactionDetailGasTooltipConversion')}
|
||||
</a>
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
position="right"
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
detailTitleColor={Color.textDefault}
|
||||
detailText={
|
||||
showCurrencyRateCheck && (
|
||||
<Box className="gas-display__currency-container">
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={SECONDARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={Boolean(useNativeCurrencyAsPrimaryCurrency)}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
detailTotal={
|
||||
<Box className="gas-display__currency-container">
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
type={PRIMARY}
|
||||
value={hexMinimumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
subText={
|
||||
<>
|
||||
<Box
|
||||
key="editGasSubTextFeeLabel"
|
||||
display={DISPLAY.INLINE_FLEX}
|
||||
className={classNames('gas-display__gas-fee-label', {
|
||||
'gas-display__gas-fee-warning': estimateUsed === 'high',
|
||||
})}
|
||||
>
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<Box marginRight={1}>
|
||||
<strong>
|
||||
{estimateUsed === 'high' && '⚠ '}
|
||||
{t('editGasSubTextFeeLabel')}
|
||||
</strong>
|
||||
</Box>
|
||||
<Box
|
||||
key="editGasSubTextFeeValue"
|
||||
className="gas-display__currency-container"
|
||||
>
|
||||
<LoadingHeartBeat estimateUsed={estimateUsed} />
|
||||
<UserPreferencedCurrencyDisplay
|
||||
key="editGasSubTextFeeAmount"
|
||||
type={PRIMARY}
|
||||
value={hexMaximumTransactionFee}
|
||||
hideLabel={!useNativeCurrencyAsPrimaryCurrency}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
}
|
||||
subTitle={
|
||||
<GasTiming
|
||||
maxPriorityFeePerGas={hexWEIToDecGWEI(
|
||||
draftTransaction.gas.maxPriorityFeePerGas,
|
||||
)}
|
||||
maxFeePerGas={hexWEIToDecGWEI(
|
||||
draftTransaction.gas.maxFeePerGas,
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>,
|
||||
<ConfirmGasDisplay key="gas-display" />,
|
||||
(gasError || isInsufficientTokenError) && (
|
||||
<TransactionDetailItem
|
||||
key="total-item"
|
||||
|
@ -35,7 +35,6 @@
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
&__currency-container,
|
||||
&__total-amount,
|
||||
&__total-value {
|
||||
position: relative;
|
||||
|
@ -197,7 +197,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
|
||||
Gas
|
||||
</div>
|
||||
<span
|
||||
class="box box--margin-bottom-1 box--flex-direction-row typography gas-display__title__estimate typography--p typography--weight-normal typography--style-italic typography--color-text-muted"
|
||||
class="gas-details-item-title__estimate"
|
||||
>
|
||||
(
|
||||
estimated
|
||||
@ -236,7 +236,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--margin-left-1 box--flex-direction-row box--text-align-right typography typography--h6 typography--weight-bold typography--style-normal typography--color-text-default"
|
||||
>
|
||||
<div
|
||||
class="box gas-display__currency-container box--flex-direction-row"
|
||||
class="gas-details-item__currency-container"
|
||||
>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
@ -274,7 +274,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
|
||||
class="box box--margin-top-1 box--margin-bottom-1 box--flex-direction-row typography transaction-detail-item__row-subText typography--h7 typography--weight-normal typography--style-normal typography--align-end typography--color-text-alternative"
|
||||
>
|
||||
<div
|
||||
class="box gas-display__gas-fee-label box--display-inline-flex box--flex-direction-row"
|
||||
class="box gas-details-item__gasfee-label box--display-inline-flex box--flex-direction-row"
|
||||
>
|
||||
<div
|
||||
class="box box--margin-right-1 box--flex-direction-row"
|
||||
@ -284,7 +284,7 @@ exports[`SendContent Component render should match snapshot 1`] = `
|
||||
</strong>
|
||||
</div>
|
||||
<div
|
||||
class="box gas-display__currency-container box--flex-direction-row"
|
||||
class="gas-details-item__currency-container"
|
||||
>
|
||||
<div
|
||||
class="currency-display-component"
|
||||
|
@ -79,6 +79,9 @@ const state = {
|
||||
confirmTransaction: {
|
||||
txData: {},
|
||||
},
|
||||
send: {
|
||||
draftTransactions: {},
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('../../store/actions', () => ({
|
||||
|
64
ui/selectors/institutional/selectors.js
Normal file
64
ui/selectors/institutional/selectors.js
Normal file
@ -0,0 +1,64 @@
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import { getSelectedIdentity, getAccountType, getProvider } from '../selectors';
|
||||
|
||||
export function getWaitForConfirmDeepLinkDialog(state) {
|
||||
return state.metamask.waitForConfirmDeepLinkDialog;
|
||||
}
|
||||
|
||||
export function getTransactionStatusMap(state) {
|
||||
return state.metamask.custodyStatusMaps;
|
||||
}
|
||||
|
||||
export function getCustodyAccountDetails(state) {
|
||||
return state.metamask.custodyAccountDetails;
|
||||
}
|
||||
|
||||
export function getCustodyAccountSupportedChains(state, address) {
|
||||
return state.metamask.custodianSupportedChains
|
||||
? state.metamask.custodianSupportedChains[toChecksumAddress(address)]
|
||||
: [];
|
||||
}
|
||||
|
||||
export function getMmiPortfolioEnabled(state) {
|
||||
return state.metamask.mmiConfiguration?.portfolio?.enabled;
|
||||
}
|
||||
|
||||
export function getMmiPortfolioUrl(state) {
|
||||
return state.metamask.mmiConfiguration?.portfolio?.url;
|
||||
}
|
||||
|
||||
export function getConfiguredCustodians(state) {
|
||||
return state.metamask.mmiConfiguration?.custodians || [];
|
||||
}
|
||||
|
||||
export function getCustodianIconForAddress(state, address) {
|
||||
let custodianIcon;
|
||||
|
||||
const checksummedAddress = toChecksumAddress(address);
|
||||
if (state.metamask.custodyAccountDetails?.[checksummedAddress]) {
|
||||
const { custodianName } =
|
||||
state.metamask.custodyAccountDetails[checksummedAddress];
|
||||
custodianIcon = state.metamask.mmiConfiguration?.custodians?.find(
|
||||
(custodian) => custodian.name === custodianName,
|
||||
)?.iconUrl;
|
||||
}
|
||||
|
||||
return custodianIcon;
|
||||
}
|
||||
|
||||
export function getIsCustodianSupportedChain(state) {
|
||||
const selectedIdentity = getSelectedIdentity(state);
|
||||
const accountType = getAccountType(state);
|
||||
const provider = getProvider(state);
|
||||
|
||||
const supportedChains =
|
||||
accountType === 'custody'
|
||||
? getCustodyAccountSupportedChains(state, selectedIdentity.address)
|
||||
: null;
|
||||
|
||||
return supportedChains?.supportedChains
|
||||
? supportedChains.supportedChains.includes(
|
||||
Number(provider.chainId).toString(),
|
||||
)
|
||||
: true;
|
||||
}
|
151
ui/selectors/institutional/selectors.test.js
Normal file
151
ui/selectors/institutional/selectors.test.js
Normal file
@ -0,0 +1,151 @@
|
||||
import { toChecksumAddress } from 'ethereumjs-util';
|
||||
import {
|
||||
getConfiguredCustodians,
|
||||
getCustodianIconForAddress,
|
||||
getCustodyAccountDetails,
|
||||
getCustodyAccountSupportedChains,
|
||||
getMmiPortfolioEnabled,
|
||||
getMmiPortfolioUrl,
|
||||
getTransactionStatusMap,
|
||||
getWaitForConfirmDeepLinkDialog,
|
||||
getIsCustodianSupportedChain,
|
||||
} from './selectors';
|
||||
|
||||
describe('Institutional selectors', () => {
|
||||
const state = {
|
||||
metamask: {
|
||||
provider: {
|
||||
type: 'test',
|
||||
chainId: '1',
|
||||
},
|
||||
identities: {
|
||||
'0x5Ab19e7091dD208F352F8E727B6DCC6F8aBB6275': {
|
||||
name: 'Custody Account A',
|
||||
address: '0x5Ab19e7091dD208F352F8E727B6DCC6F8aBB6275',
|
||||
},
|
||||
},
|
||||
selectedAddress: '0x5Ab19e7091dD208F352F8E727B6DCC6F8aBB6275',
|
||||
waitForConfirmDeepLinkDialog: '123',
|
||||
keyrings: [
|
||||
{
|
||||
type: 'Custody',
|
||||
accounts: ['0x5Ab19e7091dD208F352F8E727B6DCC6F8aBB6275'],
|
||||
},
|
||||
],
|
||||
custodyStatusMaps: '123',
|
||||
custodyAccountDetails: {
|
||||
'0x5Ab19e7091dD208F352F8E727B6DCC6F8aBB6275': {
|
||||
custodianName: 'saturn',
|
||||
},
|
||||
},
|
||||
custodianSupportedChains: {
|
||||
'0x5Ab19e7091dD208F352F8E727B6DCC6F8aBB6275': {
|
||||
supportedChains: ['1', '2'],
|
||||
custodianName: 'saturn',
|
||||
},
|
||||
},
|
||||
mmiConfiguration: {
|
||||
portfolio: {
|
||||
enabled: true,
|
||||
url: 'https://dashboard.metamask-institutional.io',
|
||||
},
|
||||
custodians: [
|
||||
{
|
||||
type: 'saturn',
|
||||
name: 'saturn',
|
||||
apiUrl: 'https://saturn-custody.dev.metamask-institutional.io',
|
||||
iconUrl: 'images/saturn.svg',
|
||||
displayName: 'Saturn Custody',
|
||||
production: true,
|
||||
refreshTokenUrl: null,
|
||||
isNoteToTraderSupported: false,
|
||||
version: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('getWaitForConfirmDeepLinkDialog', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getWaitForConfirmDeepLinkDialog(state);
|
||||
expect(result).toStrictEqual(state.metamask.waitForConfirmDeepLinkDialog);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustodyAccountDetails', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getCustodyAccountDetails(state);
|
||||
expect(result).toStrictEqual(state.metamask.custodyAccountDetails);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTransactionStatusMap', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getTransactionStatusMap(state);
|
||||
expect(result).toStrictEqual(state.metamask.custodyStatusMaps);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustodianSupportedChains', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getCustodyAccountSupportedChains(
|
||||
state,
|
||||
'0x5ab19e7091dd208f352f8e727b6dcc6f8abb6275',
|
||||
);
|
||||
expect(result).toStrictEqual(
|
||||
state.metamask.custodianSupportedChains[
|
||||
toChecksumAddress('0x5ab19e7091dd208f352f8e727b6dcc6f8abb6275')
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMmiPortfolioEnabled', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getMmiPortfolioEnabled(state);
|
||||
expect(result).toStrictEqual(
|
||||
state.metamask.mmiConfiguration.portfolio.enabled,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMmiPortfolioUrl', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getMmiPortfolioUrl(state);
|
||||
expect(result).toStrictEqual(
|
||||
state.metamask.mmiConfiguration.portfolio.url,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConfiguredCustodians', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getConfiguredCustodians(state);
|
||||
expect(result).toStrictEqual(state.metamask.mmiConfiguration.custodians);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCustodianIconForAddress', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getCustodianIconForAddress(
|
||||
state,
|
||||
'0x5ab19e7091dd208f352f8e727b6dcc6f8abb6275',
|
||||
);
|
||||
|
||||
expect(result).toStrictEqual(
|
||||
state.metamask.mmiConfiguration.custodians[0].iconUrl,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getIsCustodianSupportedChain', () => {
|
||||
it('extracts a state property', () => {
|
||||
const result = getIsCustodianSupportedChain(
|
||||
state,
|
||||
'0x5ab19e7091dd208f352f8e727b6dcc6f8abb6275',
|
||||
);
|
||||
expect(result).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
@ -228,6 +228,12 @@ export function getAccountType(state) {
|
||||
const currentKeyring = getCurrentKeyring(state);
|
||||
const type = currentKeyring && currentKeyring.type;
|
||||
|
||||
///: BEGIN:ONLY_INCLUDE_IN(mmi)
|
||||
if (type.startsWith('Custody')) {
|
||||
return 'custody';
|
||||
}
|
||||
///: END:ONLY_INCLUDE_IN
|
||||
|
||||
switch (type) {
|
||||
case KeyringType.trezor:
|
||||
case KeyringType.ledger:
|
||||
|
Loading…
Reference in New Issue
Block a user