diff --git a/packages/assets-controllers/src/AssetsContractController.test.ts b/packages/assets-controllers/src/AssetsContractController.test.ts index 8600501e0ef..f0689f4bf33 100644 --- a/packages/assets-controllers/src/AssetsContractController.test.ts +++ b/packages/assets-controllers/src/AssetsContractController.test.ts @@ -9,22 +9,20 @@ import { } from '@metamask/controller-utils'; import HttpProvider from '@metamask/ethjs-provider-http'; import type { - NetworkClientId, - NetworkControllerMessenger, Provider, + NetworkControllerEvents, + NetworkControllerActions, } from '@metamask/network-controller'; import { NetworkController, NetworkClientType, } from '@metamask/network-controller'; -import { - getDefaultPreferencesState, - type PreferencesState, -} from '@metamask/preferences-controller'; +import { getDefaultPreferencesState } from '@metamask/preferences-controller'; import assert from 'assert'; import { mockNetwork } from '../../../tests/mock-network'; import { buildInfuraNetworkClientConfiguration } from '../../network-controller/tests/helpers'; +import type { AllowedActions, AllowedEvents } from './AssetsContractController'; import { AssetsContractController, MISSING_PROVIDER_ERROR, @@ -65,22 +63,25 @@ async function setupAssetContractControllers({ } = {}) { const networkClientConfiguration = { type: NetworkClientType.Infura, - network: 'mainnet', + network: NetworkType.mainnet, infuraProjectId, chainId: BUILT_IN_NETWORKS.mainnet.chainId, ticker: BUILT_IN_NETWORKS.mainnet.ticker, } as const; let provider: Provider; - const messenger: NetworkControllerMessenger = - new ControllerMessenger().getRestricted({ - name: 'NetworkController', - allowedActions: [], - allowedEvents: [], - }); + const controllerMessenger = new ControllerMessenger< + NetworkControllerActions | AllowedActions, + NetworkControllerEvents | AllowedEvents + >(); + const networkMessenger = controllerMessenger.getRestricted({ + name: 'NetworkController', + allowedActions: [], + allowedEvents: [], + }); const networkController = new NetworkController({ infuraProjectId, - messenger, + messenger: networkMessenger, trackMetaMetricsEvent: jest.fn(), }); if (useNetworkControllerProvider) { @@ -94,41 +95,27 @@ async function setupAssetContractControllers({ ); } - const getNetworkClientById = useNetworkControllerProvider - ? networkController.getNetworkClientById.bind(networkController) - : (networkClientId: NetworkClientId) => - ({ - ...networkController.getNetworkClientById(networkClientId), - provider, - // TODO: Replace `any` with type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as any); - - const preferencesStateChangeListeners: ((state: PreferencesState) => void)[] = - []; + const assetsContractMessenger = controllerMessenger.getRestricted({ + name: 'AssetsContractController', + allowedActions: ['NetworkController:getNetworkClientById'], + allowedEvents: [ + 'PreferencesController:stateChange', + 'NetworkController:networkDidChange', + ], + }); const assetsContract = new AssetsContractController({ chainId: ChainId.mainnet, - onPreferencesStateChange: (listener) => { - preferencesStateChangeListeners.push(listener); - }, - onNetworkDidChange: (listener) => - messenger.subscribe('NetworkController:networkDidChange', listener), - getNetworkClientById, + messenger: assetsContractMessenger, ...options, }); return { - messenger, + messenger: controllerMessenger, network: networkController, assetsContract, provider, networkClientConfiguration, infuraProjectId, - triggerPreferencesStateChange: (state: PreferencesState) => { - for (const listener of preferencesStateChangeListeners) { - listener(state); - } - }, }; } @@ -170,32 +157,41 @@ export { setupAssetContractControllers, mockNetworkWithDefaultChainId }; describe('AssetsContractController', () => { it('should set default config', async () => { const { assetsContract, messenger } = await setupAssetContractControllers(); - expect(assetsContract.config).toStrictEqual({ + expect({ + chainId: assetsContract.chainId, + ipfsGateway: assetsContract.ipfsGateway, + }).toStrictEqual({ chainId: SupportedTokenDetectionNetworks.mainnet, ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, - provider: undefined, }); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); }); it('should update the ipfsGateWay config value when this value is changed in the preferences controller', async () => { - const { assetsContract, messenger, triggerPreferencesStateChange } = - await setupAssetContractControllers(); - expect(assetsContract.config).toStrictEqual({ + const { assetsContract, messenger } = await setupAssetContractControllers(); + expect({ + chainId: assetsContract.chainId, + ipfsGateway: assetsContract.ipfsGateway, + }).toStrictEqual({ chainId: SupportedTokenDetectionNetworks.mainnet, ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, - provider: undefined, }); - triggerPreferencesStateChange({ - ...getDefaultPreferencesState(), - ipfsGateway: 'newIPFSGateWay', - }); + messenger.publish( + 'PreferencesController:stateChange', + { + ...getDefaultPreferencesState(), + ipfsGateway: 'newIPFSGateWay', + }, + [], + ); - expect(assetsContract.config).toStrictEqual({ + expect({ + chainId: assetsContract.chainId, + ipfsGateway: assetsContract.ipfsGateway, + }).toStrictEqual({ ipfsGateway: 'newIPFSGateWay', chainId: SupportedTokenDetectionNetworks.mainnet, - provider: undefined, }); messenger.clearEventSubscriptions('NetworkController:networkDidChange'); @@ -211,7 +207,7 @@ describe('AssetsContractController', () => { it('should throw missing provider error when getting ERC-20 token balance when missing provider', async () => { const { assetsContract, messenger } = await setupAssetContractControllers(); - assetsContract.configure({ provider: undefined }); + assetsContract.provider = undefined; await expect( assetsContract.getERC20BalanceOf( ERC20_UNI_ADDRESS, @@ -223,7 +219,7 @@ describe('AssetsContractController', () => { it('should throw missing provider error when getting ERC-20 token decimal when missing provider', async () => { const { assetsContract, messenger } = await setupAssetContractControllers(); - assetsContract.configure({ provider: undefined }); + assetsContract.provider = undefined; await expect( assetsContract.getERC20TokenDecimals(ERC20_UNI_ADDRESS), ).rejects.toThrow(MISSING_PROVIDER_ERROR); @@ -233,7 +229,7 @@ describe('AssetsContractController', () => { it('should get balance of ERC-20 token contract correctly', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -287,7 +283,7 @@ describe('AssetsContractController', () => { it('should get ERC-721 NFT tokenId correctly', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -320,7 +316,7 @@ describe('AssetsContractController', () => { it('should throw missing provider error when getting ERC-721 token standard and details when missing provider', async () => { const { assetsContract, messenger } = await setupAssetContractControllers(); - assetsContract.configure({ provider: undefined }); + assetsContract.provider = undefined; await expect( assetsContract.getTokenStandardAndDetails( ERC20_UNI_ADDRESS, @@ -333,7 +329,7 @@ describe('AssetsContractController', () => { it('should throw contract standard error when getting ERC-20 token standard and details when provided with invalid ERC-20 address', async () => { const { assetsContract, messenger, provider } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; const error = 'Unable to determine contract standard'; await expect( assetsContract.getTokenStandardAndDetails( @@ -347,7 +343,7 @@ describe('AssetsContractController', () => { it('should get ERC-721 token standard and details', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -412,7 +408,7 @@ describe('AssetsContractController', () => { it('should get ERC-1155 token standard and details', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -497,7 +493,7 @@ describe('AssetsContractController', () => { it('should get ERC-20 token standard and details', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -594,7 +590,7 @@ describe('AssetsContractController', () => { it('should get ERC-721 NFT tokenURI correctly', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -643,7 +639,7 @@ describe('AssetsContractController', () => { it('should not throw an error when address given does not support NFT Metadata interface', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; const errorLogSpy = jest .spyOn(console, 'error') .mockImplementationOnce(() => { @@ -701,7 +697,7 @@ describe('AssetsContractController', () => { it('should get ERC-721 NFT name', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -731,7 +727,7 @@ describe('AssetsContractController', () => { it('should get ERC-721 NFT symbol', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -771,7 +767,7 @@ describe('AssetsContractController', () => { it('should get ERC-20 token decimals', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -803,7 +799,7 @@ describe('AssetsContractController', () => { it('should get ERC-20 token name', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -835,7 +831,7 @@ describe('AssetsContractController', () => { it('should get ERC-721 NFT ownership', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -876,7 +872,7 @@ describe('AssetsContractController', () => { it('should get balance of ERC-20 token in a single call on network with token detection support', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -982,7 +978,7 @@ describe('AssetsContractController', () => { useNetworkControllerProvider: true, infuraProjectId, }); - assetsContract.configure({ provider }); + assetsContract.provider = provider; const balancesOnMainnet = await assetsContract.getBalancesInSingleCall( ERC20_SAI_ADDRESS, @@ -1011,7 +1007,7 @@ describe('AssetsContractController', () => { provider, networkClientConfiguration, } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -1082,7 +1078,7 @@ describe('AssetsContractController', () => { it('should throw missing provider error when transferring single ERC-1155 when missing provider', async () => { const { assetsContract, messenger } = await setupAssetContractControllers(); - assetsContract.configure({ provider: undefined }); + assetsContract.provider = undefined; await expect( assetsContract.transferSingleERC1155( ERC1155_ADDRESS, @@ -1098,7 +1094,7 @@ describe('AssetsContractController', () => { it('should throw when ERC1155 function transferSingle is not defined', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -1135,7 +1131,7 @@ describe('AssetsContractController', () => { it('should get the balance of a ERC-1155 NFT for a given address', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ @@ -1181,7 +1177,7 @@ describe('AssetsContractController', () => { it('should get the URI of a ERC-1155 NFT', async () => { const { assetsContract, messenger, provider, networkClientConfiguration } = await setupAssetContractControllers(); - assetsContract.configure({ provider }); + assetsContract.provider = provider; mockNetworkWithDefaultChainId({ networkClientConfiguration, mocks: [ diff --git a/packages/assets-controllers/src/AssetsContractController.ts b/packages/assets-controllers/src/AssetsContractController.ts index 06e72dce348..80b9e122483 100644 --- a/packages/assets-controllers/src/AssetsContractController.ts +++ b/packages/assets-controllers/src/AssetsContractController.ts @@ -1,15 +1,14 @@ import { Contract } from '@ethersproject/contracts'; import { Web3Provider } from '@ethersproject/providers'; -import type { BaseConfig, BaseState } from '@metamask/base-controller'; -import { BaseControllerV1 } from '@metamask/base-controller'; +import type { RestrictedControllerMessenger } from '@metamask/base-controller'; import { IPFS_DEFAULT_GATEWAY_URL } from '@metamask/controller-utils'; import type { NetworkClientId, - NetworkState, - NetworkController, + NetworkControllerGetNetworkClientByIdAction, + NetworkControllerNetworkDidChangeEvent, Provider, } from '@metamask/network-controller'; -import type { PreferencesState } from '@metamask/preferences-controller'; +import type { PreferencesControllerStateChangeEvent } from '@metamask/preferences-controller'; import type { Hex } from '@metamask/utils'; import type BN from 'bn.js'; import abiSingleCallBalancesContract from 'single-call-balance-checker-abi'; @@ -67,105 +66,85 @@ export const SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID: Record = { export const MISSING_PROVIDER_ERROR = 'AssetsContractController failed to set the provider correctly. A provider must be set for this method to be available'; -/** - * @type AssetsContractConfig - * - * Assets Contract controller configuration - * @property provider - Provider used to create a new web3 instance - */ -// This interface was created before this ESLint rule was added. -// Convert to a `type` in a future major version. -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export interface AssetsContractConfig extends BaseConfig { - provider: Provider | undefined; - ipfsGateway: string; - chainId: Hex; -} - /** * @type BalanceMap * * Key value object containing the balance for each tokenAddress * @property [tokenAddress] - Address of the token */ -// This interface was created before this ESLint rule was added. -// Convert to a `type` in a future major version. -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export interface BalanceMap { +export type BalanceMap = { [tokenAddress: string]: BN; -} +}; + +const name = 'AssetsContractController'; + +export type AllowedActions = NetworkControllerGetNetworkClientByIdAction; + +export type AllowedEvents = + | PreferencesControllerStateChangeEvent + | NetworkControllerNetworkDidChangeEvent; + +export type AssetsContractControllerMessenger = RestrictedControllerMessenger< + typeof name, + AllowedActions, + AllowedEvents, + AllowedActions['type'], + AllowedEvents['type'] +>; /** * Controller that interacts with contracts on mainnet through web3 */ -export class AssetsContractController extends BaseControllerV1< - AssetsContractConfig, - BaseState -> { - private _provider?: Provider; +export class AssetsContractController { + protected messagingSystem: AssetsContractControllerMessenger; - /** - * Name of this controller used during composition - */ - override name = 'AssetsContractController' as const; + #provider: Provider | undefined; - private readonly getNetworkClientById: NetworkController['getNetworkClientById']; + ipfsGateway: string; + + chainId: Hex; /** * Creates a AssetsContractController instance. * * @param options - The controller options. + * @param options.messenger - * @param options.chainId - The chain ID of the current network. - * @param options.onPreferencesStateChange - Allows subscribing to preference controller state changes. - * @param options.onNetworkDidChange - Allows subscribing to network controller networkDidChange events. - * @param options.getNetworkClientById - Gets the network client with the given id from the NetworkController. - * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. */ - constructor( - { - chainId: initialChainId, - onPreferencesStateChange, - onNetworkDidChange, - getNetworkClientById, - }: { - chainId: Hex; - onPreferencesStateChange: ( - listener: (preferencesState: PreferencesState) => void, - ) => void; - onNetworkDidChange: ( - listener: (networkState: NetworkState) => void, - ) => void; - getNetworkClientById: NetworkController['getNetworkClientById']; - }, - config?: Partial, - state?: Partial, - ) { - super(config, state); - this.defaultConfig = { - provider: undefined, - ipfsGateway: IPFS_DEFAULT_GATEWAY_URL, - chainId: initialChainId, - }; - this.initialize(); - this.getNetworkClientById = getNetworkClientById; - - onPreferencesStateChange(({ ipfsGateway }) => { - this.configure({ ipfsGateway }); - }); - - onNetworkDidChange(({ selectedNetworkClientId }) => { - const selectedNetworkClient = getNetworkClientById( - selectedNetworkClientId, - ); - const { chainId } = selectedNetworkClient.configuration; - - if (this.config.chainId !== chainId) { - this.configure({ - chainId: selectedNetworkClient.configuration.chainId, - }); - } - }); + constructor({ + messenger, + chainId: initialChainId, + }: { + messenger: AssetsContractControllerMessenger; + chainId: Hex; + }) { + this.chainId = initialChainId; + this.#provider = undefined; + this.ipfsGateway = IPFS_DEFAULT_GATEWAY_URL; + this.messagingSystem = messenger; + + this.messagingSystem.subscribe( + `PreferencesController:stateChange`, + ({ ipfsGateway }) => { + this.ipfsGateway = ipfsGateway; + }, + ); + + this.messagingSystem.subscribe( + `NetworkController:networkDidChange`, + ({ selectedNetworkClientId }) => { + const { + configuration: { chainId }, + } = this.messagingSystem.call( + `NetworkController:getNetworkClientById`, + selectedNetworkClientId, + ); + + if (this.chainId !== chainId) { + this.chainId = chainId; + } + }, + ); } /** @@ -175,8 +154,8 @@ export class AssetsContractController extends BaseControllerV1< * * @property provider - Provider used to create a new underlying Web3 instance */ - set provider(provider: Provider) { - this._provider = provider; + set provider(provider: Provider | undefined) { + this.#provider = provider; } get provider() { @@ -191,8 +170,11 @@ export class AssetsContractController extends BaseControllerV1< */ getProvider(networkClientId?: NetworkClientId): Web3Provider { const provider = networkClientId - ? this.getNetworkClientById(networkClientId).provider - : this._provider; + ? this.messagingSystem.call( + `NetworkController:getNetworkClientById`, + networkClientId, + ).provider + : this.#provider; if (provider === undefined) { throw new Error(MISSING_PROVIDER_ERROR); @@ -210,8 +192,11 @@ export class AssetsContractController extends BaseControllerV1< */ getChainId(networkClientId?: NetworkClientId): Hex { return networkClientId - ? this.getNetworkClientById(networkClientId).configuration.chainId - : this.config.chainId; + ? this.messagingSystem.call( + `NetworkController:getNetworkClientById`, + networkClientId, + ).configuration.chainId + : this.chainId; } /** @@ -338,15 +323,13 @@ export class AssetsContractController extends BaseControllerV1< // Asserts provider is available this.getProvider(networkClientId); - const { ipfsGateway } = this.config; - // ERC721 try { const erc721Standard = this.getERC721Standard(networkClientId); return { ...(await erc721Standard.getDetails( tokenAddress, - ipfsGateway, + this.ipfsGateway, tokenId, )), }; @@ -360,7 +343,7 @@ export class AssetsContractController extends BaseControllerV1< return { ...(await erc1155Standard.getDetails( tokenAddress, - ipfsGateway, + this.ipfsGateway, tokenId, )), }; diff --git a/packages/assets-controllers/src/AssetsContractControllerWithNetworkClientId.test.ts b/packages/assets-controllers/src/AssetsContractControllerWithNetworkClientId.test.ts index d064192b686..4fad8648c02 100644 --- a/packages/assets-controllers/src/AssetsContractControllerWithNetworkClientId.test.ts +++ b/packages/assets-controllers/src/AssetsContractControllerWithNetworkClientId.test.ts @@ -120,7 +120,7 @@ describe('AssetsContractController with NetworkClientId', () => { }, ], }); - const tokenId = await assetsContract.getERC721NftTokenId( + const tokenId = assetsContract.getERC721NftTokenId( ERC721_GODS_ADDRESS, '0x9a90bd8d1149a88b42a99cf62215ad955d6f498a', 0, diff --git a/packages/assets-controllers/src/NftDetectionController.test.ts b/packages/assets-controllers/src/NftDetectionController.test.ts index 3b2fa76f727..06d5f80250d 100644 --- a/packages/assets-controllers/src/NftDetectionController.test.ts +++ b/packages/assets-controllers/src/NftDetectionController.test.ts @@ -1003,7 +1003,7 @@ describe('NftDetectionController', () => { it('should do nothing when the request to Nft API fails', async () => { const selectedAccount = createMockInternalAccount({ address: '0x3' }); nock(NFT_API_BASE_URL) - .get(`/users/${selectedAccount.address}/tokens`) + .get(`/users/${String(selectedAccount.address)}/tokens`) .query({ continuation: '', limit: '50', diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 88cf60275f2..82b468428f9 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -1,5 +1,12 @@ export * from './AccountTrackerController'; -export * from './AssetsContractController'; +export type { + BalanceMap, + AssetsContractControllerMessenger, +} from './AssetsContractController'; +export { + SINGLE_CALL_BALANCES_ADDRESS_BY_CHAINID, + AssetsContractController, +} from './AssetsContractController'; export * from './CurrencyRateController'; export type { NftControllerState, diff --git a/packages/network-controller/src/create-auto-managed-network-client.ts b/packages/network-controller/src/create-auto-managed-network-client.ts index 543c6582815..05a13cb324b 100644 --- a/packages/network-controller/src/create-auto-managed-network-client.ts +++ b/packages/network-controller/src/create-auto-managed-network-client.ts @@ -97,9 +97,7 @@ export function createAutoManagedNetworkClient< // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any return function (this: unknown, ...args: any[]) { - // @ts-expect-error We don't care that `this` may not be compatible - // with the signature of the method being called, as technically - // it can be anything. + // @ts-expect-error We don't care that `this` may not be compatible with the signature of the method being called, as technically it can be anything. return value.apply(this === receiver ? provider : this, args); }; } @@ -150,9 +148,7 @@ export function createAutoManagedNetworkClient< // TODO: Replace `any` with type // eslint-disable-next-line @typescript-eslint/no-explicit-any return function (this: unknown, ...args: any[]) { - // @ts-expect-error We don't care that `this` may not be - // compatible with the signature of the method being called, as - // technically it can be anything. + // @ts-expect-error We don't care that `this` may not be compatible with the signature of the method being called, as technically it can be anything. return value.apply(this === receiver ? blockTracker : this, args); }; }