Skip to content

Commit

Permalink
Merge branch 'develop' into fix/26621-contact-names-should-not-allow-…
Browse files Browse the repository at this point in the history
…duplication
  • Loading branch information
Matt561 authored Nov 4, 2024
2 parents d83c7dd + eab6233 commit 20e2625
Show file tree
Hide file tree
Showing 17 changed files with 202 additions and 57 deletions.
28 changes: 13 additions & 15 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1007,6 +1007,7 @@ export default class MetamaskController extends EventEmitter {
state: initState.TokenRatesController,
messenger: tokenRatesMessenger,
tokenPricesService: new CodefiTokenPricesServiceV2(),
disabled: !this.preferencesController.state.useCurrencyRateCheck,
});

this.controllerMessenger.subscribe(
Expand All @@ -1015,9 +1016,9 @@ export default class MetamaskController extends EventEmitter {
const { useCurrencyRateCheck: prevUseCurrencyRateCheck } = prevState;
const { useCurrencyRateCheck: currUseCurrencyRateCheck } = currState;
if (currUseCurrencyRateCheck && !prevUseCurrencyRateCheck) {
this.tokenRatesController.start();
this.tokenRatesController.enable();
} else if (!currUseCurrencyRateCheck && prevUseCurrencyRateCheck) {
this.tokenRatesController.stop();
this.tokenRatesController.disable();
}
}, this.preferencesController.state),
);
Expand Down Expand Up @@ -2590,12 +2591,6 @@ export default class MetamaskController extends EventEmitter {

const preferencesControllerState = this.preferencesController.state;

const { useCurrencyRateCheck } = preferencesControllerState;

if (useCurrencyRateCheck) {
this.tokenRatesController.start();
}

if (this.#isTokenListPollingRequired(preferencesControllerState)) {
this.tokenListController.start();
}
Expand All @@ -2608,12 +2603,6 @@ export default class MetamaskController extends EventEmitter {

const preferencesControllerState = this.preferencesController.state;

const { useCurrencyRateCheck } = preferencesControllerState;

if (useCurrencyRateCheck) {
this.tokenRatesController.stop();
}

if (this.#isTokenListPollingRequired(preferencesControllerState)) {
this.tokenListController.stop();
}
Expand Down Expand Up @@ -3250,6 +3239,7 @@ export default class MetamaskController extends EventEmitter {
backup,
approvalController,
phishingController,
tokenRatesController,
// Notification Controllers
authenticationController,
userStorageController,
Expand Down Expand Up @@ -4016,6 +4006,13 @@ export default class MetamaskController extends EventEmitter {
currencyRateController,
),

tokenRatesStartPolling:
tokenRatesController.startPolling.bind(tokenRatesController),
tokenRatesStopPollingByPollingToken:
tokenRatesController.stopPollingByPollingToken.bind(
tokenRatesController,
),

// GasFeeController
gasFeeStartPollingByNetworkClientId:
gasFeeController.startPollingByNetworkClientId.bind(gasFeeController),
Expand Down Expand Up @@ -6641,12 +6638,13 @@ export default class MetamaskController extends EventEmitter {

/**
* A method that is called by the background when all instances of metamask are closed.
* Currently used to stop polling in the gasFeeController.
* Currently used to stop controller polling.
*/
onClientClosed() {
try {
this.gasFeeController.stopAllPolling();
this.currencyRateController.stopAllPolling();
this.tokenRatesController.stopAllPolling();
this.appStateController.clearPollingTokens();
} catch (error) {
console.error(error);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@
"@metamask/address-book-controller": "^6.0.0",
"@metamask/announcement-controller": "^7.0.0",
"@metamask/approval-controller": "^7.0.0",
"@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A41.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-41.0.0-57b3d695bb.patch",
"@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A42.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-42.0.0-57b3d695bb.patch",
"@metamask/base-controller": "^7.0.0",
"@metamask/bitcoin-wallet-snap": "^0.8.2",
"@metamask/browser-passworder": "^4.3.0",
Expand Down
13 changes: 13 additions & 0 deletions ui/contexts/assetPolling.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { ReactNode } from 'react';
import useCurrencyRatePolling from '../hooks/useCurrencyRatePolling';
import useTokenRatesPolling from '../hooks/useTokenRatesPolling';

// This provider is a step towards making controller polling fully UI based.
// Eventually, individual UI components will call the use*Polling hooks to
// poll and return particular data. This polls globally in the meantime.
export const AssetPollingProvider = ({ children }: { children: ReactNode }) => {
useCurrencyRatePolling();
useTokenRatesPolling();

return <>{children}</>;
};
13 changes: 0 additions & 13 deletions ui/contexts/currencyRate.js

This file was deleted.

57 changes: 57 additions & 0 deletions ui/hooks/useMultiPolling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useEffect, useState } from 'react';

type UseMultiPollingOptions<PollingInput> = {
startPolling: (input: PollingInput) => Promise<string>;
stopPollingByPollingToken: (pollingToken: string) => void;
input: PollingInput[];
};

// A hook that manages multiple polling loops of a polling controller.
// Callers provide an array of inputs, and the hook manages starting
// and stopping polling loops for each input.
const useMultiPolling = <PollingInput>(
usePollingOptions: UseMultiPollingOptions<PollingInput>,
) => {
const [polls, setPolls] = useState(new Map());

useEffect(() => {
// start new polls
for (const input of usePollingOptions.input) {
const key = JSON.stringify(input);
if (!polls.has(key)) {
usePollingOptions
.startPolling(input)
.then((token) =>
setPolls((prevPolls) => new Map(prevPolls).set(key, token)),
);
}
}

// stop existing polls
for (const [inputKey, token] of polls.entries()) {
const exists = usePollingOptions.input.some(
(i) => inputKey === JSON.stringify(i),
);

if (!exists) {
usePollingOptions.stopPollingByPollingToken(token);
setPolls((prevPolls) => {
const newPolls = new Map(prevPolls);
newPolls.delete(inputKey);
return newPolls;
});
}
}
}, [usePollingOptions.input && JSON.stringify(usePollingOptions.input)]);

// stop all polling on dismount
useEffect(() => {
return () => {
for (const token of polls.values()) {
usePollingOptions.stopPollingByPollingToken(token);
}
};
}, []);
};

export default useMultiPolling;
40 changes: 40 additions & 0 deletions ui/hooks/useTokenRatesPolling.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useSelector } from 'react-redux';
import {
getMarketData,
getNetworkConfigurationsByChainId,
getTokenExchangeRates,
getTokensMarketData,
getUseCurrencyRateCheck,
} from '../selectors';
import {
tokenRatesStartPolling,
tokenRatesStopPollingByPollingToken,
} from '../store/actions';
import useMultiPolling from './useMultiPolling';

const useTokenRatesPolling = ({ chainIds }: { chainIds?: string[] } = {}) => {
// Selectors to determine polling input
const useCurrencyRateCheck = useSelector(getUseCurrencyRateCheck);
const networkConfigurations = useSelector(getNetworkConfigurationsByChainId);

// Selectors returning state updated by the polling
const tokenExchangeRates = useSelector(getTokenExchangeRates);
const tokensMarketData = useSelector(getTokensMarketData);
const marketData = useSelector(getMarketData);

useMultiPolling({
startPolling: tokenRatesStartPolling,
stopPollingByPollingToken: tokenRatesStopPollingByPollingToken,
input: useCurrencyRateCheck
? chainIds ?? Object.keys(networkConfigurations)
: [],
});

return {
tokenExchangeRates,
tokensMarketData,
marketData,
};
};

export default useTokenRatesPolling;
6 changes: 3 additions & 3 deletions ui/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
LegacyMetaMetricsProvider,
} from '../contexts/metametrics';
import { MetamaskNotificationsProvider } from '../contexts/metamask-notifications';
import { CurrencyRateProvider } from '../contexts/currencyRate';
import { AssetPollingProvider } from '../contexts/assetPolling';
import ErrorPage from './error';
import Routes from './routes';

Expand Down Expand Up @@ -49,11 +49,11 @@ class Index extends PureComponent {
<LegacyMetaMetricsProvider>
<I18nProvider>
<LegacyI18nProvider>
<CurrencyRateProvider>
<AssetPollingProvider>
<MetamaskNotificationsProvider>
<Routes />
</MetamaskNotificationsProvider>
</CurrencyRateProvider>
</AssetPollingProvider>
</LegacyI18nProvider>
</I18nProvider>
</LegacyMetaMetricsProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const InteractiveReplacementTokenPage: React.FC = () => {

const filteredAccounts = custodianAccounts.filter(
(account: TokenAccount) =>
// @ts-expect-error metaMaskAccounts isn't a real type
metaMaskAccounts[account.address.toLowerCase()],
);

Expand All @@ -163,6 +164,7 @@ const InteractiveReplacementTokenPage: React.FC = () => {
name: account.name,
labels: account.labels,
balance:
// @ts-expect-error metaMaskAccounts isn't a real type
metaMaskAccounts[account.address.toLowerCase()]?.balance || 0,
}),
);
Expand Down
2 changes: 1 addition & 1 deletion ui/pages/notifications-settings/notifications-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export default function NotificationsSettings() {
const isUpdatingMetamaskNotifications = useSelector(
getIsUpdatingMetamaskNotifications,
);
const accounts: AccountType[] = useSelector(getInternalAccounts);
const accounts = useSelector(getInternalAccounts) as AccountType[];

// States
const [loadingAllowNotifications, setLoadingAllowNotifications] =
Expand Down
9 changes: 9 additions & 0 deletions ui/selectors/accounts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
hasCreatedBtcMainnetAccount,
hasCreatedBtcTestnetAccount,
getSelectedInternalAccount,
getInternalAccounts,
} from './accounts';

const MOCK_STATE: AccountsState = {
Expand All @@ -27,6 +28,14 @@ const MOCK_STATE: AccountsState = {
};

describe('Accounts Selectors', () => {
describe('#getInternalAccounts', () => {
it('returns a list of internal accounts', () => {
expect(getInternalAccounts(mockState as AccountsState)).toStrictEqual(
Object.values(mockState.metamask.internalAccounts.accounts),
);
});
});

describe('#getSelectedInternalAccount', () => {
it('returns selected internalAccount', () => {
expect(
Expand Down
5 changes: 4 additions & 1 deletion ui/selectors/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
isBtcMainnetAddress,
isBtcTestnetAddress,
} from '../../shared/lib/multichain';
import { getInternalAccounts } from './selectors';

export type AccountsState = {
metamask: AccountsControllerState;
Expand All @@ -20,6 +19,10 @@ function isBtcAccount(account: InternalAccount) {
return Boolean(account && account.type === P2wpkh);
}

export function getInternalAccounts(state: AccountsState) {
return Object.values(state.metamask.internalAccounts.accounts);
}

export function getSelectedInternalAccount(state: AccountsState) {
const accountId = state.metamask.internalAccounts.selectedAccount;
return state.metamask.internalAccounts.accounts[accountId];
Expand Down
22 changes: 17 additions & 5 deletions ui/selectors/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ import {
getOrderedConnectedAccountsForConnectedDapp,
getSubjectMetadata,
} from './permissions';
import { getSelectedInternalAccount } from './accounts';
import { getSelectedInternalAccount, getInternalAccounts } from './accounts';
import { createDeepEqualSelector } from './util';
import { getMultichainBalances, getMultichainNetwork } from './multichain';

Expand Down Expand Up @@ -371,10 +371,6 @@ export function getSelectedInternalAccountWithBalance(state) {
return selectedAccountWithBalance;
}

export function getInternalAccounts(state) {
return Object.values(state.metamask.internalAccounts.accounts);
}

export function getInternalAccount(state, accountId) {
return state.metamask.internalAccounts.accounts[accountId];
}
Expand Down Expand Up @@ -582,11 +578,27 @@ export const getTokenExchangeRates = (state) => {
);
};

/**
* Get market data for tokens on the current chain
*
* @param state
* @returns {Record<Hex, import('@metamask/assets-controllers').MarketDataDetails>}
*/
export const getTokensMarketData = (state) => {
const chainId = getCurrentChainId(state);
return state.metamask.marketData?.[chainId];
};

/**
* Get market data for tokens across all chains
*
* @param state
* @returns {Record<Hex, Record<Hex, import('@metamask/assets-controllers').MarketDataDetails>>}
*/
export const getMarketData = (state) => {
return state.metamask.marketData;
};

export function getAddressBook(state) {
const chainId = getCurrentChainId(state);
if (!state.metamask.addressBook[chainId]) {
Expand Down
8 changes: 0 additions & 8 deletions ui/selectors/selectors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,6 @@ describe('Selectors', () => {
});
});

describe('#getInternalAccounts', () => {
it('returns a list of internal accounts', () => {
expect(selectors.getInternalAccounts(mockState)).toStrictEqual(
Object.values(mockState.metamask.internalAccounts.accounts),
);
});
});

describe('#getInternalAccount', () => {
it("returns undefined if the account doesn't exist", () => {
expect(
Expand Down
3 changes: 2 additions & 1 deletion ui/selectors/snaps/accounts.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { createSelector } from 'reselect';
import { AccountsControllerState } from '@metamask/accounts-controller';
import { getAccountName, getInternalAccounts } from '../selectors';
import { getAccountName } from '../selectors';
import { getInternalAccounts } from '../accounts';
import { createDeepEqualSelector } from '../util';

/**
Expand Down
Loading

0 comments on commit 20e2625

Please sign in to comment.