Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plan consolidated API for TokenDetectionController + DetectTokensController #1813

Closed
mcmire opened this issue Oct 11, 2023 · 6 comments
Closed
Assignees

Comments

@mcmire
Copy link
Contributor

mcmire commented Oct 11, 2023

Look into how TokenDetectionController (from this repo) is being used in the mobile app (including the patches for this controller in mobile) and how DetectTokensController is being used in the extension, and determine an API for a combined controller that satisfies the requirements that extension and mobile have.

@MajorLift
Copy link
Contributor

MajorLift commented Dec 6, 2023

Consolidated TokenDetectionController API

  • Note: remove all properties from TokenDetection{Config,State} and use private class fields instead.

Constructor

  • deferPollingStart (optional)
  • interval (optional)
  • chainId (required)
  • selectedAddress (optional)
  • messenger (required)

Fields

  • super.#intervalLength

  • #intervalId

  • #chainId

  • #selectedAddress

  • #isDetectionEnabledFromPreference

  • #isDetectionEnabledForNetwork

  • Callbacks for communicating with BaseControllerV1 controllers:

    • #onPreferenceStateChange
    • #getBalancesInSingleCall
    • #addDetectedTokens
    • #getTokensState
    • #getPreferencesState

Methods

polling-controller:

  • super.{g,s}etIntervalLength
  • startPolling()
  • stopPolling()
  • _executePoll()
  • restartTokenDetection()

(remove start, stop)

core:

  • detectTokens (merge with detectNewTokens())
    • Refactor - extract methods:
      • updateTokens()
      • getTokensToDetectSlices()
      • operationToSafelyExecute()
    • @throws: MetaMask - DetectTokensController single call balance fetch failed
    • Add this.trackMetaMetricsEvent call
  • getCorrectChainId() (merge with getChainIdFromNetworkStore())

extension:

  • #registerKeyringHandlers()
  • isActive()
  • trackMetaMetricsEvent()

mobile:

  • updateTokensName(): (tokenList) => tokensController.updateTokensName(tokenList)
const { tokens } = this.getTokensState();
...
if (tokens.length && !tokens[0].name) {
	this.updateTokensName(tokenList);
}

remove

  • extension:
    • interval accessors
    • tokenList setter

@mcmire
Copy link
Contributor Author

mcmire commented Dec 11, 2023

@MajorLift Is this essentially blocked by #3625? Would you need to revisit this once that's merged?

@MajorLift
Copy link
Contributor

Any changes to the TokenDetectionController API that are made in #3625 will need to be reflected here as well, but it doesn't look like there will be any significant departures from what I have right now.

Mostly I need a sign-off on the plan in the comment above before I can get started on #3626. That can wait until after #3625 is merged, though.

@mcmire
Copy link
Contributor Author

mcmire commented Dec 13, 2023

It is good that you've noted some of the ways that each implementation captures data internally, but the list is fairly large for what is a small controller. I wonder if it would be better to list the public API that we want to have rather than all of the private properties and methods that are present. How we end up storing data internally or refactoring the logic, we can decide when we create the consolidated version, but I don't know if that's important for now.

Here's what I see as the public API:

  • Everything from BaseController and StaticIntervalPollingController (we can't change this, so it stays)
    • name instance property
    • metadata instance property
    • state getter
    • setIntervalLength
    • getIntervalLength
    • startPollingByNetworkClientId
    • stopAllPolling
    • stopPollingByPollingToken
    • onPollingCompleteByNetworkClientId
    • _executePoll (not part of the public API, but something that needs to be implemented)
  • detectTokens (or detectNewTokens — whatever we want to call it)

That said, I know I'm sort of changing the purpose of this ticket, but in addition to reviewing the public API, perhaps we should also take this time to audit the behavior between the three implementations as well, and capture a list of differences there. When creating a consolidated version of the controller I imagine we'll want to do some refactoring, so we'll want to make sure we preserve any nuances.

I see you've done this a bit already: you've alluded to the fact that DetectTokensController (extension implementation) keeps track of the locked/unlocked state of the wallet, and calls the method to detect tokens when the wallet is unlocked, and refuses to detect tokens when it's locked.

Besides this, it looks like there may be other slight differences in the constructor in TokenDetectionController vs. DetectTokensController. Specifically, in TokenDetectionController, when TokenListController state changes, the method to detect tokens is called only when the tokens collection is non-empty; but in DetectTokensController, it's always called.

Also, detectTokens in TokenDetectionController is slightly different from detectNewTokens in DetectTokensController:

  • Whereas TokenDetectionController caches the selected chain and whether or not it supports token detection, DetectTokensController reads the selected chain every time it's called
  • Whereas TokenDetectionController aborts if token detection has been disabled in preferences, DetectTokensController does not abort if token detection has been disabled in preferences but the current chain is mainnet; instead, a static list of tokens is used as the base (so as not to make any additional network requests?).
  • Whereas TokenDetectionController calls TokensController.addDetectedTokens with the tokens from TokenListController's tokenList collection which are not in TokensController's tokens collection, not in TokensController's ignoredTokens collection, and have a non-zero balance, DetectTokensController goes one step further and excludes tokens which are already in TokensController's detectedTokens collection.

@mcmire
Copy link
Contributor Author

mcmire commented Dec 13, 2023

Between what you've noted and what I've noted, I imagine we probably have enough to go on, but if you find any other differences feel free to add them here.

I'll let you close this ticket when you feel you have enough information to proceed to #3626.

MajorLift added a commit that referenced this issue Dec 22, 2023
…ticIntervalPollingController` (#3609)

## Explanation

This upgrades `TokenDetectionController` to extend `BaseControllerV2`
and `StaticIntervalPollingController` as a preparation step for merging
`TokenDetectionController` with `DetectTokensController`.

## References

- See #1813 
- See #1509
- Closes #3625

## Changelog

### Added
- `TokenListController` now exports a `TokenListControllerMessenger`
type ([#3609](#3609)).
- `TokenDetectionController` exports types
`TokenDetectionControllerMessenger`, `TokenDetectionControllerActions`,
`TokenDetectionControllerGetStateAction`,
`TokenDetectionControllerEvents`,
`TokenDetectionControllerStateChangeEvent`
([#3609](#3609)).
- Add `enable` and `disable` methods to `TokenDetectionController`,
which control whether the controller is able to make polling requests or
all of its network calls are blocked.
([#3609](#3609)).
- Note that if the controller is initiated without the `disabled`
constructor option set to `false`, the `enable` method will need to be
called before the controller can make polling requests in response to
subscribed events.

### Changed
- **BREAKING:** `TokenDetectionController` is upgraded to extend
`BaseControllerV2` and `StaticIntervalPollingController`
([#3609](#3609)).
- The constructor now expects an options object as its only argument,
with required properties `messenger`, `networkClientId`, required
callbacks `onPreferencesStateChange`, `getBalancesInSingleCall`,
`addDetectedTokens`, `getTokenState`, `getPreferencesState`, and
optional properties `disabled`, `interval`, `selectedAddress`.
  
## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've highlighted breaking changes using the "BREAKING" category
above as appropriate

---------

Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
@MajorLift
Copy link
Contributor

MajorLift commented Jan 5, 2024

Notes:

  • Constructor
    • Wherever DetectTokensController uses restartTokenDetection, replace detectTokens
    • Subscribe to AccountsController:selectedAccountChange event
    • Avoid triggering TokenListController:stateChange for empty tokens list (TokenDetectionController)
  • Replace #disabled checks with isActive
  • detectTokens:
    • tokens-controller: apply messenger pattern
      • Subscribe to tokens-controller: maintain cache of ignoredTokens, detectedTokens
      • Exclude tokens already in detectedTokens list from detectTokens logic
    • Replace isTokenDetectionInactiveInMainnet is true -> early exit with detectTokens check for #isDetectionEnabledFromPreferences.
      • OR copy logic for generating static mainnet token list into core? reach out to mobile team on importance of handling this case explicitly.
    • Accept _trackMetaMetricsEvent callback in constructor
      • Generate and pass eventTokensDetails
    • try-catch block is handled by _safelyExecute()
  • restartTokenDetection
    • triggers detectTokens and resets interval length to default
    • all other logic moved into detectTokens

MajorLift added a commit that referenced this issue Feb 8, 2024
…` from extension (#3775)

## Explanation

Merges the core `TokenDetectionController` with `DetectTokensController`
from the extension and patches for this controller from mobile.

- Aims to replace the extension and mobile versions of this controller
and implement a unified interface that consolidates the functionality of
all three iterations.
- Generally assumes `DetectTokensController` to be more up-to-date, and
prioritizes preserving extension and mobile behavior to minimize
disruption.

Significant API changes: see
[Changelog](https://github.com/MetaMask/core/pull/3775/files#diff-ee47d03d53776b8dd530799a8047f5e32e36e35765620aeb50b294adc3339fab)
for details.

## References

- Closes #3626
- See #1813 

## Changelog

###
[`@metamask/assets-controllers`](https://github.com/MetaMask/core/pull/3775/files#diff-ee47d03d53776b8dd530799a8047f5e32e36e35765620aeb50b294adc3339fab)
### Added

- **BREAKING:** Adds `@metamask/accounts-controller` ^8.0.0 and
`@metamask/keyring-controller` ^12.0.0 as dependencies and peer
dependencies. ([#3775](#3775)).
- **BREAKING:** `TokenDetectionController` newly subscribes to the
`PreferencesController:stateChange`,
`AccountsController:selectedAccountChange`, `KeyringController:lock`,
`KeyringController:unlock` events, and allows the
`PreferencesController:getState` messenger action.
([#3775](#3775))

### Changed

- **BREAKING:** `TokenDetectionController` is merged with
`DetectTokensController` from the `metamask-extension` repo.
([#3775](#3775))
- **BREAKING:** `TokenDetectionController` now resets its polling
interval to the default value of 3 minutes when token detection is
triggered by external controller events `KeyringController:unlock`,
`TokenListController:stateChange`, `PreferencesController:stateChange`,
`AccountsController:selectedAccountChange`.
- **BREAKING:** `TokenDetectionController` now refetches tokens on
`NetworkController:networkDidChange` if the `networkClientId` is changed
instead of `chainId`.
- **BREAKING:** `TokenDetectionController` cannot initiate polling or
token detection if `KeyringController` state is locked.
- **BREAKING:** The `detectTokens` method now excludes tokens that are
already included in the `TokensController`'s `detectedTokens` list from
the batch of incoming tokens it sends to the `TokensController`
`addDetectedTokens` method.
- **BREAKING:** The constructor for `TokenDetectionController` expects a
new required proprerty `trackMetaMetricsEvent`, which defines the
callback that is called in the `detectTokens` method.
- **BREAKING:** In Mainnet, even if the `PreferenceController`'s
`useTokenDetection` option is set to false, automatic token detection is
performed on the legacy token list (token data from the
contract-metadata repo).

### Removed

- **BREAKING:** `TokenDetectionController` constructor no longer accepts
options `onPreferencesStateChange`, `getPreferencesState`.
([#3775](#3775))
- **BREAKING:** `TokenDetectionController` no longer allows the
`NetworkController:stateChange` event. The
`NetworkController:networkDidChange` event can be used instead.
([#3775](#3775))

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've highlighted breaking changes using the "BREAKING" category
above as appropriate

---------

Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
MajorLift added a commit that referenced this issue Feb 27, 2024
…dates (#3923)

## Explanation

As a preparatory step for fully replacing the extension
`DetectTokensController` with the consolidated core repo
`TokenDetectionController`, `TokenDetectionController` needs to be
updated with changes made to the extension `DetectTokensController`
since #1813 was closed.

#### Diff of `DetectTokensController` state
-
[MetaMask/metamask-extension@`5d285f7be5f7be981995dfa725aad97d81cc990a..85cd1c89039e900b452edb704ec37e9ccbd3e76a`#diff-323d0cf464](https://github.com/MetaMask/metamask-extension/compare/5d285f7be5f7be981995dfa725aad97d81cc990a..85cd1c89039e900b452edb704ec37e9ccbd3e76a#diff-323d0cf46498be3850b971474905354ea5ccf7fa13745ad1e6eba59c5b586830)

### Differences from extension `DetectTokensController`

- Refactors logic for retrieving `chainId`, `networkClientId` into
`this.#getCorrectChainIdAndNetworkClientId`
- Uses `getNetworkConfigurationByNetworkClientId` action instead of
`getNetworkClientById` to retrieve `chainId`.
- If `networkClientId` is not supplied to the method, or it's supplied
but `getNetworkConfigurationByNetworkClientId` returns `undefined`,
finds `chainId` from `providerConfig`.

- `detectTokens` replaces `detectNewTokens`
- `detectTokens` accepts options object `{ selectedAddress,
networkClientId }` instead of `{ selectedAddress, chainId,
networkClientId }`.
- Does not throw error if `getBalancesInSingleCall` fails. Also does not
exit early -- continues looping.
- Passes lists of full `Token` types to
`TokensController:addDetectedTokens` instead of objects containing only
`{ address, decimals, symbol }`.
  
- `#trackMetaMetricsEvents` is a private method instead of protected.
- Passes string literals instead of extension shared constants into
`_trackMetaMetricsEvent`.

## References

- Partially implements #3916
- Blocking #3918
- Changes adopted from:
  - MetaMask/metamask-extension#22898
  - MetaMask/metamask-extension#22814
  - #3914
  - MetaMask/metamask-extension#21437
- Blocking (Followed by) #3938

## Changelog

###
[`@metamask/assets-controllers`](https://github.com/MetaMask/core/pull/3923/files#diff-ee47d03d53776b8dd530799a8047f5e32e36e35765620aeb50b294adc3339fab)

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've highlighted breaking changes using the "BREAKING" category
above as appropriate

---------

Co-authored-by: jiexi <jiexiluan@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants