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

feat: added DeFiPositionsController #5400

Open
wants to merge 53 commits into
base: main
Choose a base branch
from

Conversation

bergarces
Copy link
Contributor

@bergarces bergarces commented Feb 26, 2025

Explanation

This PR adds a new controller that will be used to fetch DeFi positions for both extension and mobile.

It does so on network state or account change.

Draft PR for extension: MetaMask/metamask-extension#31751
Draft PR for mobile: MetaMask/metamask-mobile#13925

References

Changelog

Pending Changelog edits

@metamask/assets-controllers

  • ADDED: Added DeFiPositionsController

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've highlighted breaking changes using the "BREAKING" category above as appropriate
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@bergarces bergarces force-pushed the feat/MMASSETS-553-create-defi-controller branch from e6486b7 to 5a1fed2 Compare February 28, 2025 13:34
);

this.messagingSystem.subscribe(
'NetworkController:stateChange',
Copy link
Contributor Author

@bergarces bergarces Feb 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this subscription because we need the positions to be fetched when logging into the wallet, and sicne the selectedAccountChange is not triggered at the start, this seems to be the only way I found to do it.

I'd appreciate to know if there's a more idiomatic way of doing it, as otherwise there's no need to refetch positions every time the chain changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eeem i think you can use the event unlock from the keyring controller for that.
here is an example:

  // Subscribe to keyring lock/unlock events.
    this.messagingSystem.subscribe('KeyringController:lock', () => {
      // Do something
    });
    this.messagingSystem.subscribe('KeyringController:unlock', () => {
      // Do something
    });

Copy link
Contributor

@Prithpal-Sooriya Prithpal-Sooriya Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to fire something when the wallet is unlocked, we can try using the keyring controller?
Example

Copy link
Contributor

@Prithpal-Sooriya Prithpal-Sooriya Apr 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only concern with subscribing to :stateChange is that this is very frequent.

If we need to use state change subscripions, consider using the 3rd parameter when creating a subscription to use a selector to reduce calls.

this.messagingSystem.subscribe(
  'NetworkController:stateChange', // event name
  (prevState, newState) => { ... }, // handler
  (state) => state.foo, // selector
)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per @salimtb suggestion, I have now moved towards lock and unlock keyring events. They represent the intention of what we want better and they allow us to start and stop the polling.

@bergarces bergarces changed the title controller initial version feat: added DeFiPositionsController Feb 28, 2025
@jpsains
Copy link

jpsains commented Mar 7, 2025

Need to update readme

@bergarces bergarces force-pushed the feat/MMASSETS-553-create-defi-controller branch 3 times, most recently from 53f5b3d to 5bb3ea5 Compare March 14, 2025 13:12
@bergarces bergarces requested a review from a team as a code owner April 10, 2025 09:25
@bergarces
Copy link
Contributor Author

@metamaskbot publish-preview

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/accounts-controller": "27.0.0-preview-20c091f4",
  "@metamask-previews/address-book-controller": "6.0.3-preview-20c091f4",
  "@metamask-previews/announcement-controller": "7.0.3-preview-20c091f4",
  "@metamask-previews/app-metadata-controller": "1.0.0-preview-20c091f4",
  "@metamask-previews/approval-controller": "7.1.3-preview-20c091f4",
  "@metamask-previews/assets-controllers": "56.0.0-preview-20c091f4",
  "@metamask-previews/base-controller": "8.0.0-preview-20c091f4",
  "@metamask-previews/bridge-controller": "13.0.0-preview-20c091f4",
  "@metamask-previews/bridge-status-controller": "12.0.0-preview-20c091f4",
  "@metamask-previews/build-utils": "3.0.3-preview-20c091f4",
  "@metamask-previews/chain-agnostic-permission": "0.3.0-preview-20c091f4",
  "@metamask-previews/composable-controller": "11.0.0-preview-20c091f4",
  "@metamask-previews/controller-utils": "11.7.0-preview-20c091f4",
  "@metamask-previews/earn-controller": "0.11.0-preview-20c091f4",
  "@metamask-previews/eip1193-permission-middleware": "0.1.0-preview-20c091f4",
  "@metamask-previews/ens-controller": "16.0.0-preview-20c091f4",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-20c091f4",
  "@metamask-previews/gas-fee-controller": "23.0.0-preview-20c091f4",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-20c091f4",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-20c091f4",
  "@metamask-previews/keyring-controller": "21.0.2-preview-20c091f4",
  "@metamask-previews/logging-controller": "6.0.4-preview-20c091f4",
  "@metamask-previews/message-manager": "12.0.1-preview-20c091f4",
  "@metamask-previews/multichain": "4.0.0-preview-20c091f4",
  "@metamask-previews/multichain-api-middleware": "0.1.1-preview-20c091f4",
  "@metamask-previews/multichain-network-controller": "0.3.0-preview-20c091f4",
  "@metamask-previews/multichain-transactions-controller": "0.9.0-preview-20c091f4",
  "@metamask-previews/name-controller": "8.0.3-preview-20c091f4",
  "@metamask-previews/network-controller": "23.2.0-preview-20c091f4",
  "@metamask-previews/notification-services-controller": "5.0.1-preview-20c091f4",
  "@metamask-previews/permission-controller": "11.0.6-preview-20c091f4",
  "@metamask-previews/permission-log-controller": "3.0.3-preview-20c091f4",
  "@metamask-previews/phishing-controller": "12.4.1-preview-20c091f4",
  "@metamask-previews/polling-controller": "13.0.0-preview-20c091f4",
  "@metamask-previews/preferences-controller": "17.0.0-preview-20c091f4",
  "@metamask-previews/profile-sync-controller": "11.0.1-preview-20c091f4",
  "@metamask-previews/queued-request-controller": "10.0.0-preview-20c091f4",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-20c091f4",
  "@metamask-previews/remote-feature-flag-controller": "1.6.0-preview-20c091f4",
  "@metamask-previews/sample-controllers": "0.1.0-preview-20c091f4",
  "@metamask-previews/selected-network-controller": "22.0.0-preview-20c091f4",
  "@metamask-previews/signature-controller": "27.1.0-preview-20c091f4",
  "@metamask-previews/token-search-discovery-controller": "2.1.0-preview-20c091f4",
  "@metamask-previews/transaction-controller": "54.0.0-preview-20c091f4",
  "@metamask-previews/user-operation-controller": "33.0.0-preview-20c091f4"
}

return groupPositions(defiPositionsResponse);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
return null;
Copy link
Contributor Author

@bergarces bergarces Apr 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how we are going to represent that an error has occurred.

It is not the same as the entry for the address being undefined, which can happen whilst loading.

No point on creating new fields if we do not have custom messages for them.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: maybe we can remove the // eslint-disable-next-line @typescript-eslint/no-unused-vars
and just
catch { return null }

'AccountsController:listAccounts',
);

const results = await reduceInBatchesSerially({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this handy function lying around. I would have preferred something like p-queue, which doesn't do batches but limits how many promises execute at once, but really not worth adding a new dependency for that.

We set a healthy limit of 10 addresses at a time, we might have to increase or reduce it.

@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Add a new DeFiPositionsController that keeps an update list of DeFi positions for EVM accounts ([#5400](https://github.com/MetaMask/core/pull/5400))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(we can do this when releasing) - the changelog may need to go into more detail on the new additions.

  • the new Controller class. The Actions/Events it takes.
  • Any new exported types or code a user can use.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added additional details to the changelog so it's easier for the release.

);

this.update((state) => {
state.allDeFiPositions = allDefiPositions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The batching logic above is relatively new to me. Does it continue with next batch and we filter out batches that fail?

Reason I'm asking, is here we overwrite the defi positions. Is there a possibility of us overwriting some positions for an address that didn't resolve?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This will overwrite the whole positions object.

If an address didn't resolve, then that means it errored, and we shouldn't be displaying those positions anyway as they could be stale.

],
"include": ["../../types", "./src"]
"include": ["../../types", "./src"],
"exclude": ["**/*.test.ts", "**/__fixtures__/"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job excluding this!

This build config is extended from a shared build config.

IDK if we are overwriting or extend the original list of exclusions:

"exclude": ["./jest.config.packages.ts", "**/*.test.ts", "**/jest.config.ts"]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It overwrites it, but it is fine to do it this way as the global patterns don't cover __fixtures__ directories and we are excluding **/*.test.ts.

We do not need to worry about **/jest.config.ts as it's in the root of the package, outside of src, and therefore not part of the include.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants