Skip to content

Conversation

@mcmire
Copy link
Contributor

@mcmire mcmire commented Nov 20, 2024

Currently, SwapsController relies on the global network. It uses the global provider from the NetworkController and uses it to get the allowance for a user's token on a chain, and it uses the chain ID of the global network (again, coming from NetworkController) to retrieve parameters that will ultimately be used to control when and how swap quotes are requested and how to transform the resulting data. Finally, it also expects setProvider and/or setChainId to get called when the global network is switched, when it will restore previously cached quote data from state so that it can be presumably reshown in the UI.

Long-term, we want to remove the concept of the global network from the extension, so we need to prepare this controller accordingly. Fundamentally, we need to have the parts of the API which rely on the global provider to take a network client ID, which will be used to fetch a provider and chain ID.

A couple of things to note about this controller which prohibits us from completely removing the reliance on the global network:

  • Besides setChainId and setProvider, there is another entry point to the API via startFetchAndSetQuotes. This starts a loop to fetch quotes on a particular chain. Crucially, this loop does not support multiple simultaneous network access. If the loop is already active for a network and startFetchAndSetQuotes is called for another, the loop will be reset and all quotes captured for the previous network will be overwritten. We plan on keeping this behavior.
  • As alluded to above, the controller caches swaps quotes it fetches for a chain. To retain the previous behavior of restoring from cache when the network is switched, the controller now listens for the switch via NetworkController:networkDidChange.

Ultimately both of these things may need to be changed when the concept of the global network is completely removed, but we can revisit this at a later time.

See the changelog for the full list of changes.

Fixes #204.


Also see PR in mobile repo to upgrade to these changes: MetaMask/metamask-mobile#12378. This PR should pass CI and the manual testing steps.

@mcmire mcmire force-pushed the remove-reliance-on-global-network branch 2 times, most recently from df05d2b to 5f3d2e3 Compare November 20, 2024 20:12
@socket-security
Copy link

socket-security bot commented Nov 20, 2024

New and removed dependencies detected. Learn more about Socket for GitHub ↗︎

Package New capabilities Transitives Size Publisher
npm/@types/jest@29.5.14 None 0 78.8 kB types

🚮 Removed packages: npm/@jest/types@26.6.2, npm/@types/jest@26.0.24, npm/@types/yargs@15.0.19, npm/diff-sequences@26.6.2, npm/jest-diff@26.6.2, npm/jest-get-type@26.3.0, npm/react-is@17.0.2

View full report↗︎

@mcmire mcmire force-pushed the remove-reliance-on-global-network branch from 5f3d2e3 to c771181 Compare November 20, 2024 20:28
Currently, SwapsController relies on the global network. It uses the
global provider from the NetworkController and uses it to get the
allowance for a user's token on a chain, and it uses the chain ID of the
global network (again, coming from NetworkController) to retrieve
parameters that will ultimately be used to control when and how swap
quotes are requested and how to transform the resulting data. Finally,
it also expects `setProvider` and/or `setChainId` to get called when the
global network is switched, when it will restore previously cached quote
data from state so that it can be presumably reshown in the UI.

Long-term, we want to remove the concept of the global network from the
extension, so we need to prepare this controller accordingly.
Fundamentally, we need to have the parts of the API which rely on the
global provider to take a network client ID, which will be used to fetch
a provider and chain ID.

A couple of things to note about this controller which prohibits us from
completely removing the reliance on the global network:

- Besides `setChainId` and `setProvider`, there is another entry point to
the API via `startFetchAndSetQuotes`. This starts a loop to fetch quotes
on a particular chain. Crucially, this loop does not support multiple
simultaneous network access. If the loop is already active for a network
and `startFetchAndSetQuotes` is called for another, the loop will be
reset and all quotes captured for the previous network will be
overwritten. We plan on keeping this behavior.
- As alluded to above, the controller caches swaps quotes it fetches for
a chain. To retain the previous behavior of restoring from cache when
the network is switched, the controller now listens for the switch via
`NetworkController:networkDidChange`.

Ultimately both of these things may need to be changed when the concept
of the global network is completely removed, but we can revisit this at
a later time.

See the changelog for the full list of changes.
@mcmire mcmire force-pushed the remove-reliance-on-global-network branch from c771181 to ed63b7a Compare November 20, 2024 20:55
@@ -216,529 +370,3098 @@ describe('SwapsController', () => {
swapsUtilEstimateGas.mockRestore();
});

it('should set default options', () => {
Copy link
Contributor Author

@mcmire mcmire Nov 20, 2024

Choose a reason for hiding this comment

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

You may want to view this diff in side-by-side mode, as it's a bit hard to read.

I wanted to verify that networkClientId was being added to the fetch params metadata correctly, but these tests did not seem right, and so I fixed them.


swapsController.__test__updatePrivate('#supportedChainIds', [chainId]);
// @ts-expect-error Intentionally not providing full NetworkState object.
rootMessenger.publish('NetworkController:networkDidChange', {
Copy link
Contributor Author

@mcmire mcmire Nov 20, 2024

Choose a reason for hiding this comment

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

I am not using the messengerMock set up in the beforeEach because I want to actually publish the event, and publish is mocked there.

chainId: '0x1',
rpcUrl: 'test',
} as unknown as Provider;
describe('fetchTokenWithCache', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These tests previously existed under the "tokens cache" describe. They did not seem right as they were using a "private" method to set the state necessary to set up the test, when we can just pass that state to the SwapsController constructor. They also did not test the use of the mutex in fetchTokenWithCache. I rewrote them to address these two problems and made sure that we also confirm that the given networkClientId is used correctly.

topAssetsLastFetched: 2912,
aggregatorMetadataLastFetched: 2913,
};
describe('fetchTopAssetsWithCache', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These tests previously existed under the "top assets cache" describe. I rewrote them in a similar manner as the fetchTokenWithCache tests.

The tests for fetchTopAssetsWithCache are similar to the ones for fetchTokenWithCache since the implementation is similar.

});
await swapsController.fetchTopAssetsWithCache();
expect(swapsUtilFetchTopAssets).toHaveBeenCalled();
describe('fetchAggregatorMetadataWithCache', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These tests previously existed under the "aggregator metadata cache" describe. I rewrote them in a similar manner as the fetchTokenWithCache tests.

The tests for fetchTopAssetsWithCache are similar to the ones for fetchTokenWithCache since the implementation is similar.

this.stopPollingAndResetState.bind(this),
);

this.messagingSystem.subscribe(
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 mentioned in the PR description, I added this because we have this concept of a "chain cache" in state and it seemed important to preserve the existing behavior around that. Listening to networkDidChange seemed like the best way to accomplish this, but happy to hear other thoughts on what we could do here.

},
"devDependencies": {
"@babel/runtime": "^7.0.0",
"@ethersproject/abi": "^5.7.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Useful to encode the data and result for the ERC20 contract in tests.

"@metamask/eslint-config-jest": "^12.1.0",
"@metamask/eslint-config-nodejs": "^12.1.0",
"@metamask/eslint-config-typescript": "^12.1.0",
"@metamask/eth-json-rpc-provider": "^4.1.6",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Used by fake-provider.ts.

"@metamask/eth-json-rpc-provider": "^4.1.6",
"@metamask/gas-fee-controller": "^21.0.0",
"@metamask/keyring-controller": "^17.0.0",
"@metamask/json-rpc-engine": "^10.0.1",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Used by fake-provider.ts.

"@metamask/network-controller": "^21.1.0",
"@metamask/transaction-controller": "^37.3.0",
"@types/jest": "^26.0.22",
"@types/jest": "^29.5.14",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Useful to gain access to the types for the runOnlyPendingTimersAsync method available in Jest 29 (which we're already using).

@mcmire
Copy link
Contributor Author

mcmire commented Nov 20, 2024

I have published @metamask-previews/swaps-controller@11.0.0-preview-8176e4502716cfe6450c2b73be6ee403b0cea072. I am working on getting this updated in Mobile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mcmire
Copy link
Contributor Author

mcmire commented Nov 22, 2024

New preview build: @metamask-previews/swaps-controller@11.0.0-preview-40be04506cf92f5e6a271eb90c96b4ddcf57dd5d. MetaMask/metamask-mobile#12378 has been updated with this build.

@mcmire mcmire marked this pull request as ready for review November 22, 2024 21:16
@mcmire mcmire requested a review from a team November 22, 2024 21:16
@mcmire mcmire self-assigned this Nov 22, 2024
@mcmire mcmire requested a review from a team November 22, 2024 21:24
.eslintrc Outdated
"@metamask/eslint-config"
]
],
"ignorePatterns": ["tests/fake-provider.ts"]
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Could this be removed? ESlint is useful for mocks, even if we need to disable some rules. I see there are a bunch of inline disable comments already

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is happening because we don't have two tsconfig files in this project; we just have the one and it excludes anything outside of src by default. As a result, typescript-eslint fails when attempting to parse this file.

I have pushed 8e847c0 which moves this file to src and renames it to fake-provider.test.ts so that it will continue to be excluded from the build. I have also instructed Jest not to run this file as a test file.

In a later PR I will split the tsconfig files as per other projects and clean this up.

mikesposito
mikesposito previously approved these changes Dec 4, 2024
Copy link
Member

@mikesposito mikesposito left a comment

Choose a reason for hiding this comment

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

Looks great! I have also tested this on mobile and the swap went through without issues.

Though, on mobile we can't switch chain while we are fetching quotes, so there are parts of code that I'm not sure how to test (or even if that's possible)

swap_test.webm

@mikesposito
Copy link
Member

This is probably unrelated to this work, but I noticed that after swapping a token for some ETH (or vice-versa) token balances are not updated until I switch network (and switch back) or restart the app. This is uncomfortable because if I want to use the token that I just received from the swap (e.g. swapping again) the UI tells me that I can't because I have 0 balance

@mcmire
Copy link
Contributor Author

mcmire commented Dec 4, 2024

This is probably unrelated to this work, but I noticed that after swapping a token for some ETH (or vice-versa) token balances are not updated until I switch network (and switch back) or restart the app.

Strange! I would agree that this is probably unrelated, since SwapsController should only be responsible for fetching quotes. This sounds more like a TokenBalancesController issue. Still, good catch. I'll see if there's a ticket for it and open one if not.

@mcmire mcmire merged commit 404a318 into main Dec 4, 2024
11 checks passed
@mcmire mcmire deleted the remove-reliance-on-global-network branch December 4, 2024 16:34
sahar-fehri pushed a commit to MetaMask/metamask-mobile that referenced this pull request Jan 8, 2025
## Description

This version upgrades `@metamask/swaps-controller` so that it is less
reliant on the global network. Specifically:

- The `setChainId` and `setProvider` methods have been removed from
SwapsController.
- The `fetchGasFeeEstimates` and `fetchEstimatedMultiLayerL1Fee`
SwapsController constructor options are now expected to take a
`networkClientId`.
- The SwapsController constructor no longer takes a `chainId` option.
- `startFetchAndSetQuotes`, `fetchTokenWithCache`,
`fetchTopAssetsWithCache`, and `fetchAggregatorMetadataWithCache` now
take a `networkClientId`.
- The `fetchParamsMetaData` SwapsController state property now includes
a `networkClientId`.
- The chain cache in SwapsController state will now automatically be
updated whenever the network has changed.

See full changelog here:
https://github.com/MetaMask/swaps-controller/blob/main/CHANGELOG.md#1200

At the moment, the global network client ID is still passed into
SwapsController whenever it is used, but now that can be changed to use
a dapp-level network client ID without needing to update
SwapsController.

## Related issues

Fixes #12470.

Also see:

- MetaMask/swaps-controller#347
- MetaMask/swaps-controller#204

## Manual testing steps

- Tap on the Swap button
- Select a destination token and proceed
- You should see new quotes
- Ideally, you should be able to complete the swaps flow and create a
transaction

## Screenshots/Recordings

The Swaps flow should work exactly like it does now.

Here is a video demonstrating fetching Swaps quotes:


https://github.com/user-attachments/assets/62a490c9-32c4-49a2-886d-136328761658

## Pre-merge author checklist

- [x] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## Pre-merge reviewer checklist

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Makes SwapsController support multichain

4 participants