Skip to content

Commit bcea6ad

Browse files
authored
Make both callback and messaging system options for TokenListController & GasFeeController when listening to NetworkController (#932)
* support either callback or messaging system subscription for NetworkController on GasFeeController and TokenListController
1 parent 4fec100 commit bcea6ad

File tree

3 files changed

+138
-39
lines changed

3 files changed

+138
-39
lines changed

src/assets/TokenListController.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,42 @@ describe('TokenListController', () => {
631631
);
632632
});
633633

634+
it('should update tokenList state when network updates are passed via onNetworkStateChange callback', async () => {
635+
nock(TOKEN_END_POINT_API)
636+
.get(`/tokens/${NetworksChainId.mainnet}`)
637+
.reply(200, sampleMainnetTokenList)
638+
.persist();
639+
640+
const controllerMessenger = getControllerMessenger();
641+
const { network } = setupNetworkController(controllerMessenger);
642+
const messenger = getRestrictedMessenger(controllerMessenger);
643+
644+
const controller = new TokenListController({
645+
chainId: NetworksChainId.mainnet,
646+
onNetworkStateChange: (callback) =>
647+
controllerMessenger.subscribe(
648+
'NetworkController:providerChange',
649+
callback,
650+
),
651+
preventPollingOnNetworkRestart: false,
652+
interval: 100,
653+
messenger,
654+
});
655+
controller.start();
656+
await new Promise<void>((resolve) => setTimeout(() => resolve(), 150));
657+
expect(controller.state.tokenList).toStrictEqual(
658+
sampleSingleChainState.tokenList,
659+
);
660+
network.setProviderType('goerli');
661+
await new Promise<void>((resolve) => setTimeout(() => resolve(), 500));
662+
663+
expect(controller.state.tokenList).toStrictEqual({});
664+
controller.destroy();
665+
controllerMessenger.clearEventSubscriptions(
666+
'NetworkController:providerChange',
667+
);
668+
});
669+
634670
it('should poll and update rate in the right interval', async () => {
635671
const tokenListMock = sinon.stub(
636672
TokenListController.prototype,

src/assets/TokenListController.ts

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ import { BaseController } from '../BaseControllerV2';
55
import type { RestrictedControllerMessenger } from '../ControllerMessenger';
66
import { safelyExecute, isTokenListSupportedForNetwork } from '../util';
77
import { fetchTokenList } from '../apis/token-service';
8-
import { NetworkControllerProviderChangeEvent } from '../network/NetworkController';
8+
import {
9+
NetworkControllerProviderChangeEvent,
10+
NetworkState,
11+
ProviderConfig,
12+
} from '../network/NetworkController';
913
import { formatAggregatorNames, formatIconUrlWithProxy } from './assetsUtil';
1014

1115
const DEFAULT_INTERVAL = 24 * 60 * 60 * 1000;
@@ -94,6 +98,7 @@ export class TokenListController extends BaseController<
9498
*
9599
* @param options - The controller options.
96100
* @param options.chainId - The chain ID of the current network.
101+
* @param options.onNetworkStateChange - A function for registering an event handler for network state changes.
97102
* @param options.interval - The polling interval, in milliseconds.
98103
* @param options.cacheRefreshThreshold - The token cache expiry time, in milliseconds.
99104
* @param options.messenger - A restricted controller messenger.
@@ -103,13 +108,17 @@ export class TokenListController extends BaseController<
103108
constructor({
104109
chainId,
105110
preventPollingOnNetworkRestart = false,
111+
onNetworkStateChange,
106112
interval = DEFAULT_INTERVAL,
107113
cacheRefreshThreshold = DEFAULT_THRESHOLD,
108114
messenger,
109115
state,
110116
}: {
111117
chainId: string;
112118
preventPollingOnNetworkRestart?: boolean;
119+
onNetworkStateChange?: (
120+
listener: (networkState: NetworkState | ProviderConfig) => void,
121+
) => void;
113122
interval?: number;
114123
cacheRefreshThreshold?: number;
115124
messenger: TokenListMessenger;
@@ -126,29 +135,54 @@ export class TokenListController extends BaseController<
126135
this.chainId = chainId;
127136
this.updatePreventPollingOnNetworkRestart(preventPollingOnNetworkRestart);
128137
this.abortController = new AbortController();
129-
this.messagingSystem.subscribe(
130-
'NetworkController:providerChange',
131-
async (providerConfig) => {
132-
if (this.chainId !== providerConfig.chainId) {
133-
this.abortController.abort();
134-
this.abortController = new AbortController();
135-
this.chainId = providerConfig.chainId;
136-
if (this.state.preventPollingOnNetworkRestart) {
137-
this.clearingTokenListData();
138-
} else {
139-
// Ensure tokenList is referencing data from correct network
140-
this.update(() => {
141-
return {
142-
...this.state,
143-
tokenList:
144-
this.state.tokensChainsCache[this.chainId]?.data || {},
145-
};
146-
});
147-
await this.restart();
148-
}
138+
if (onNetworkStateChange) {
139+
onNetworkStateChange(async (networkStateOrProviderConfig) => {
140+
// this check for "provider" is for testing purposes, since in the extension this callback will receive
141+
// an object typed as NetworkState but within repo we can only simulate as if the callback receives an
142+
// object typed as ProviderConfig
143+
if ('provider' in networkStateOrProviderConfig) {
144+
await this.#onNetworkStateChangeCallback(
145+
networkStateOrProviderConfig.provider,
146+
);
147+
} else {
148+
await this.#onNetworkStateChangeCallback(
149+
networkStateOrProviderConfig,
150+
);
149151
}
150-
},
151-
);
152+
});
153+
} else {
154+
this.messagingSystem.subscribe(
155+
'NetworkController:providerChange',
156+
async (providerConfig) => {
157+
await this.#onNetworkStateChangeCallback(providerConfig);
158+
},
159+
);
160+
}
161+
}
162+
163+
/**
164+
* Updates state and restart polling when updates are received through NetworkController subscription.
165+
*
166+
* @param providerConfig - the configuration for a provider containing critical network info.
167+
*/
168+
async #onNetworkStateChangeCallback(providerConfig: ProviderConfig) {
169+
if (this.chainId !== providerConfig.chainId) {
170+
this.abortController.abort();
171+
this.abortController = new AbortController();
172+
this.chainId = providerConfig.chainId;
173+
if (this.state.preventPollingOnNetworkRestart) {
174+
this.clearingTokenListData();
175+
} else {
176+
// Ensure tokenList is referencing data from correct network
177+
this.update(() => {
178+
return {
179+
...this.state,
180+
tokenList: this.state.tokensChainsCache[this.chainId]?.data || {},
181+
};
182+
});
183+
await this.restart();
184+
}
185+
}
152186
}
153187

154188
/**

src/gas/GasFeeController.ts

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Patch } from 'immer';
22

3+
import EthQuery from 'eth-query';
34
import { v1 as random } from 'uuid';
45
import { isHexString } from 'ethereumjs-util';
56
import { BaseController } from '../BaseControllerV2';
@@ -9,6 +10,8 @@ import type {
910
NetworkControllerGetEthQueryAction,
1011
NetworkControllerGetProviderConfigAction,
1112
NetworkControllerProviderChangeEvent,
13+
NetworkController,
14+
NetworkState,
1215
} from '../network/NetworkController';
1316
import {
1417
fetchGasEstimates,
@@ -269,6 +272,10 @@ export class GasFeeController extends BaseController<
269272
* current network is compatible with the legacy gas price API.
270273
* @param options.getCurrentAccountEIP1559Compatibility - Determines whether or not the current
271274
* account is EIP-1559 compatible.
275+
* @param options.getChainId - Returns the current chain ID.
276+
* @param options.getProvider - Returns a network provider for the current network.
277+
* @param options.onNetworkStateChange - A function for registering an event handler for the
278+
* network state change event.
272279
* @param options.legacyAPIEndpoint - The legacy gas price API URL. This option is primarily for
273280
* testing purposes.
274281
* @param options.EIP1559APIEndpoint - The EIP-1559 gas price API URL. This option is primarily
@@ -282,7 +289,10 @@ export class GasFeeController extends BaseController<
282289
state,
283290
getCurrentNetworkEIP1559Compatibility,
284291
getCurrentAccountEIP1559Compatibility,
292+
getChainId,
285293
getCurrentNetworkLegacyGasAPICompatibility,
294+
getProvider,
295+
onNetworkStateChange,
286296
legacyAPIEndpoint = LEGACY_GAS_PRICES_API_URL,
287297
EIP1559APIEndpoint = GAS_FEE_API,
288298
clientId,
@@ -293,6 +303,9 @@ export class GasFeeController extends BaseController<
293303
getCurrentNetworkEIP1559Compatibility: () => Promise<boolean>;
294304
getCurrentNetworkLegacyGasAPICompatibility: () => boolean;
295305
getCurrentAccountEIP1559Compatibility?: () => boolean;
306+
getChainId?: () => `0x${string}` | `${number}` | number;
307+
getProvider?: () => NetworkController['provider'];
308+
onNetworkStateChange?: (listener: (state: NetworkState) => void) => void;
296309
legacyAPIEndpoint?: string;
297310
EIP1559APIEndpoint?: string;
298311
clientId?: string;
@@ -315,25 +328,41 @@ export class GasFeeController extends BaseController<
315328
getCurrentAccountEIP1559Compatibility;
316329
this.EIP1559APIEndpoint = EIP1559APIEndpoint;
317330
this.legacyAPIEndpoint = legacyAPIEndpoint;
318-
const providerConfig = this.messagingSystem.call(
319-
'NetworkController:getProviderConfig',
320-
);
321-
this.currentChainId = providerConfig.chainId;
322-
this.ethQuery = this.messagingSystem.call('NetworkController:getEthQuery');
323331
this.clientId = clientId;
324-
this.messagingSystem.subscribe(
325-
'NetworkController:providerChange',
326-
async (provider) => {
327-
this.ethQuery = this.messagingSystem.call(
328-
'NetworkController:getEthQuery',
329-
);
330-
331-
if (this.currentChainId !== provider.chainId) {
332-
this.currentChainId = provider.chainId;
332+
if (onNetworkStateChange && getChainId && getProvider) {
333+
this.currentChainId = getChainId();
334+
onNetworkStateChange(async () => {
335+
const newProvider = getProvider();
336+
const newChainId = getChainId();
337+
this.ethQuery = new EthQuery(newProvider);
338+
if (this.currentChainId !== newChainId) {
339+
this.currentChainId = newChainId;
333340
await this.resetPolling();
334341
}
335-
},
336-
);
342+
});
343+
} else {
344+
const providerConfig = this.messagingSystem.call(
345+
'NetworkController:getProviderConfig',
346+
);
347+
this.currentChainId = providerConfig.chainId;
348+
this.ethQuery = this.messagingSystem.call(
349+
'NetworkController:getEthQuery',
350+
);
351+
352+
this.messagingSystem.subscribe(
353+
'NetworkController:providerChange',
354+
async (provider) => {
355+
this.ethQuery = this.messagingSystem.call(
356+
'NetworkController:getEthQuery',
357+
);
358+
359+
if (this.currentChainId !== provider.chainId) {
360+
this.currentChainId = provider.chainId;
361+
await this.resetPolling();
362+
}
363+
},
364+
);
365+
}
337366
}
338367

339368
async resetPolling() {

0 commit comments

Comments
 (0)