Skip to content

Commit

Permalink
Replace controller context (#2416)
Browse files Browse the repository at this point in the history
* Replace controller context

The `context` object previously constructed by the
`ComposableController` is no more. Instead each controller now accepts
its dependencies directly as constructor parameters, in a similar
manner to the extension controllers.

This was done in preparation for migrating to BaseControllerV2 and the
new controller messaging system - this is just a temporary solution
that will let us migrate controllers one at a time.

The style of dependency injection here matches the extension (at least
with newer controllers anyway). Specific methods and state snapshots
are injected rather than entire controllers, to help simplify unit
tests and make it easier to understand how controllers interact.

The `Engine.context` property was used throughout mobile, so it has
been preserved. It is now constructed explicitly, rather than being a
re-export of the `ComposableController` context.

This PR depends upon MetaMask/core#387

* Pass in function for `getOpenSeaApiKey` rather than string

The API key was passed in directly by accident, instead of a function get returned the key. This has been fixed.

Co-authored-by: Esteban Miño <efmino@uc.cl>

* Update `AccountTrackerController` options

The `AccountTrackerController` option `initialIdentities` was replaced
with `getIdentities`. The initial identities passed in here were
incorrect anyway due to a typo (`initialState.preferencesController`
was used instead of `initialState.PreferencesController`).

* Fix `getIdentities` handler for `AccountTrackerController`

* Set initial controller state

The `controllers` setter on `ComposedController` used to be responsible
for setting initial state. Since that setter has been removed, the
initial state is now set after the controllers have been constructed.

This should be functionally equivalent to what it was before. We're
setting the initial state by calling `update` on each controller, just
as the `controller` setter used to.

* Fix initial state variable reference

Co-authored-by: Esteban Miño <efmino@uc.cl>
  • Loading branch information
cronny25 and estebanmino committed Apr 28, 2021
1 parent 74ac0b7 commit 3957688
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 92 deletions.
201 changes: 129 additions & 72 deletions app/core/Engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,76 +65,138 @@ class Engine {
currentCurrency: 'usd'
};

this.datamodel = new ComposableController(
[
new KeyringController({ encryptor }, initialState.KeyringController),
new AccountTrackerController(),
new AddressBookController(),
new AssetsContractController(),
new AssetsController(),
new AssetsDetectionController(),
new CurrencyRateController({
nativeCurrency,
currentCurrency
}),
new PersonalMessageManager(),
new MessageManager(),
new NetworkController({
infuraProjectId: process.env.MM_INFURA_PROJECT_ID || NON_EMPTY,
providerConfig: {
static: {
eth_sendTransaction: async (payload, next, end) => {
const { TransactionController } = this.datamodel.context;
try {
const hash = await (await TransactionController.addTransaction(
payload.params[0],
payload.origin,
WalletDevice.MM_MOBILE
)).result;
end(undefined, hash);
} catch (error) {
end(error);
}
}
},
getAccounts: (end, payload) => {
const { approvedHosts, privacyMode } = store.getState();
const isEnabled = !privacyMode || approvedHosts[payload.hostname];
const { KeyringController } = this.datamodel.context;
const isUnlocked = KeyringController.isUnlocked();
const selectedAddress = this.datamodel.context.PreferencesController.state
.selectedAddress;
end(null, isUnlocked && isEnabled && selectedAddress ? [selectedAddress] : []);
const preferencesController = new PreferencesController(
{},
{
ipfsGateway: AppConstants.IPFS_DEFAULT_GATEWAY_URL
}
);
const networkController = new NetworkController({
infuraProjectId: process.env.MM_INFURA_PROJECT_ID || NON_EMPTY,
providerConfig: {
static: {
eth_sendTransaction: async (payload, next, end) => {
const { TransactionController } = this.context;
try {
const hash = await (await TransactionController.addTransaction(
payload.params[0],
payload.origin,
WalletDevice.MM_MOBILE
)).result;
end(undefined, hash);
} catch (error) {
end(error);
}
}
}),
new PhishingController(),
new PreferencesController(
{},
{
ipfsGateway: AppConstants.IPFS_DEFAULT_GATEWAY_URL
}
},
getAccounts: (end, payload) => {
const { approvedHosts, privacyMode } = store.getState();
const isEnabled = !privacyMode || approvedHosts[payload.hostname];
const { KeyringController } = this.context;
const isUnlocked = KeyringController.isUnlocked();
const selectedAddress = this.context.PreferencesController.state.selectedAddress;
end(null, isUnlocked && isEnabled && selectedAddress ? [selectedAddress] : []);
}
}
});
const assetsContractController = new AssetsContractController();
const assetsController = new AssetsController({
onPreferencesStateChange: listener => preferencesController.subscribe(listener),
onNetworkStateChange: listener => networkController.subscribe(listener),
getAssetName: assetsContractController.getAssetName.bind(assetsContractController),
getAssetSymbol: assetsContractController.getAssetSymbol.bind(assetsContractController),
getCollectibleTokenURI: assetsContractController.getCollectibleTokenURI.bind(assetsContractController)
});
const currencyRateController = new CurrencyRateController({
nativeCurrency,
currentCurrency
});

const controllers = [
new KeyringController(
{
removeIdentity: preferencesController.removeIdentity.bind(preferencesController),
syncIdentities: preferencesController.syncIdentities.bind(preferencesController),
updateIdentities: preferencesController.updateIdentities.bind(preferencesController),
setSelectedAddress: preferencesController.setSelectedAddress.bind(preferencesController)
},
{ encryptor },
initialState.KeyringController
),
new AccountTrackerController({
onPreferencesStateChange: listener => preferencesController.subscribe(listener),
getIdentities: () => preferencesController.state.identities
}),
new AddressBookController(),
assetsContractController,
assetsController,
new AssetsDetectionController({
onAssetsStateChange: listener => assetsController.subscribe(listener),
onPreferencesStateChange: listener => preferencesController.subscribe(listener),
onNetworkStateChange: listener => networkController.subscribe(listener),
getOpenSeaApiKey: () => assetsController.openSeaApiKey,
getBalancesInSingleCall: assetsContractController.getBalancesInSingleCall.bind(
assetsContractController
),
new TokenBalancesController({ interval: 10000 }),
new TokenRatesController(),
new TransactionController(),
new TypedMessageManager(),
new SwapsController({
clientId: AppConstants.SWAPS.CLIENT_ID,
fetchAggregatorMetadataThreshold: AppConstants.SWAPS.CACHE_AGGREGATOR_METADATA_THRESHOLD,
fetchTokensThreshold: AppConstants.SWAPS.CACHE_TOKENS_THRESHOLD,
fetchTopAssetsThreshold: AppConstants.SWAPS.CACHE_TOP_ASSETS_THRESHOLD
})
],
initialState
);
addTokens: assetsController.addTokens.bind(assetsController),
addCollectible: assetsController.addCollectible.bind(assetsController),
removeCollectible: assetsController.removeCollectible.bind(assetsController),
getAssetsState: () => assetsController.state
}),
currencyRateController,
new PersonalMessageManager(),
new MessageManager(),
networkController,
new PhishingController(),
preferencesController,
new TokenBalancesController(
{
onAssetsStateChange: listener => assetsController.subscribe(listener),
getSelectedAddress: () => preferencesController.state.selectedAddress,
getBalanceOf: assetsContractController.getBalanceOf.bind(assetsContractController)
},
{ interval: 10000 }
),
new TokenRatesController({
onAssetsStateChange: listener => assetsController.subscribe(listener),
onCurrencyRateStateChange: listener => currencyRateController.subscribe(listener)
}),
new TransactionController({
getNetworkState: () => networkController.state,
onNetworkStateChange: listener => networkController.subscribe(listener),
getProvider: () => networkController.provider
}),
new TypedMessageManager(),
new SwapsController({
clientId: AppConstants.SWAPS.CLIENT_ID,
fetchAggregatorMetadataThreshold: AppConstants.SWAPS.CACHE_AGGREGATOR_METADATA_THRESHOLD,
fetchTokensThreshold: AppConstants.SWAPS.CACHE_TOKENS_THRESHOLD,
fetchTopAssetsThreshold: AppConstants.SWAPS.CACHE_TOP_ASSETS_THRESHOLD
})
];

// set initial state
// TODO: Pass initial state into each controller constructor instead
// This is being set post-construction for now to ensure it's functionally equivalent with
// how the `ComponsedController` used to set initial state.
for (const controller of controllers) {
if (initialState[controller.name]) {
controller.update(initialState[controller.name]);
}
}

this.datamodel = new ComposableController(controllers, initialState);
this.context = controllers.reduce((context, controller) => {
context[controller.name] = controller;
return context;
}, {});

const {
AssetsController: assets,
KeyringController: keyring,
NetworkController: network,
TransactionController: transaction
} = this.datamodel.context;
} = this.context;

assets.setApiKey(process.env.MM_OPENSEA_KEY);
network.refreshNetwork();
Expand Down Expand Up @@ -162,7 +224,7 @@ class Engine {
NetworkController: { provider, state: NetworkControllerState },
TransactionController,
SwapsController
} = this.datamodel.context;
} = this.context;

provider.sendAsync = provider.sendAsync.bind(provider);
AccountTrackerController.configure({ provider });
Expand All @@ -181,7 +243,7 @@ class Engine {
}

refreshTransactionHistory = async forceCheck => {
const { TransactionController, PreferencesController, NetworkController } = this.datamodel.context;
const { TransactionController, PreferencesController, NetworkController } = this.context;
const { selectedAddress } = PreferencesController.state;
const { type: networkType } = NetworkController.state.provider;
const { networkId } = Networks[networkType];
Expand Down Expand Up @@ -242,7 +304,7 @@ class Engine {
AssetsController,
TokenBalancesController,
TokenRatesController
} = this.datamodel.context;
} = this.context;
const { selectedAddress } = PreferencesController.state;
const { conversionRate, currentCurrency } = CurrencyRateController.state;
const { accounts } = AccountTrackerController.state;
Expand Down Expand Up @@ -308,12 +370,7 @@ class Engine {
// Whenever we are gonna start a new wallet
// either imported or created, we need to
// get rid of the old data from state
const {
TransactionController,
AssetsController,
TokenBalancesController,
TokenRatesController
} = this.datamodel.context;
const { TransactionController, AssetsController, TokenBalancesController, TokenRatesController } = this.context;

//Clear assets info
AssetsController.update({
Expand Down Expand Up @@ -346,7 +403,7 @@ class Engine {
NetworkController,
TransactionController,
AssetsController
} = this.datamodel.context;
} = this.context;

// Select same network ?
await NetworkController.setProviderType(network.provider.type);
Expand Down Expand Up @@ -434,7 +491,7 @@ let instance;

export default {
get context() {
return instance && instance.datamodel && instance.datamodel.context;
return instance && instance.context;
},
get state() {
const {
Expand Down
28 changes: 14 additions & 14 deletions app/core/Engine.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ import Engine from './Engine';
describe('Engine', () => {
it('should expose an API', () => {
const engine = Engine.init({});
expect(engine.datamodel.context).toHaveProperty('AccountTrackerController');
expect(engine.datamodel.context).toHaveProperty('AddressBookController');
expect(engine.datamodel.context).toHaveProperty('AssetsContractController');
expect(engine.datamodel.context).toHaveProperty('AssetsController');
expect(engine.datamodel.context).toHaveProperty('AssetsDetectionController');
expect(engine.datamodel.context).toHaveProperty('CurrencyRateController');
expect(engine.datamodel.context).toHaveProperty('KeyringController');
expect(engine.datamodel.context).toHaveProperty('NetworkController');
expect(engine.datamodel.context).toHaveProperty('PersonalMessageManager');
expect(engine.datamodel.context).toHaveProperty('PhishingController');
expect(engine.datamodel.context).toHaveProperty('PreferencesController');
expect(engine.datamodel.context).toHaveProperty('TokenBalancesController');
expect(engine.datamodel.context).toHaveProperty('TokenRatesController');
expect(engine.datamodel.context).toHaveProperty('TypedMessageManager');
expect(engine.context).toHaveProperty('AccountTrackerController');
expect(engine.context).toHaveProperty('AddressBookController');
expect(engine.context).toHaveProperty('AssetsContractController');
expect(engine.context).toHaveProperty('AssetsController');
expect(engine.context).toHaveProperty('AssetsDetectionController');
expect(engine.context).toHaveProperty('CurrencyRateController');
expect(engine.context).toHaveProperty('KeyringController');
expect(engine.context).toHaveProperty('NetworkController');
expect(engine.context).toHaveProperty('PersonalMessageManager');
expect(engine.context).toHaveProperty('PhishingController');
expect(engine.context).toHaveProperty('PreferencesController');
expect(engine.context).toHaveProperty('TokenBalancesController');
expect(engine.context).toHaveProperty('TokenRatesController');
expect(engine.context).toHaveProperty('TypedMessageManager');
});
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
"dependencies": {
"@exodus/react-native-payments": "https://github.com/wachunei/react-native-payments.git#package-json-hack",
"@metamask/contract-metadata": "^1.23.0",
"@metamask/controllers": "^7.0.0",
"@metamask/controllers": "^8.0.0",
"@metamask/etherscan-link": "^2.0.0",
"@metamask/swaps-controller": "^2.0.1",
"@react-native-community/async-storage": "1.12.1",
Expand Down
15 changes: 10 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1539,12 +1539,17 @@
resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.23.0.tgz#c70be7f3eaeeb791651ce793b7cdc230e9780b18"
integrity sha512-oTUqL9dtXtbng60DZMRsBmZ5HiOUUfEsZjuswOJ0yHO24YsW0ktCcgCJVYPv1HcOsF0SVrRtG4rtrvOl4nY+HA==

"@metamask/controllers@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-7.0.0.tgz#8daecd284faa897ca1f112a3f6f28d6936dac674"
integrity sha512-vb2/wgGfJFMUa4Ej67FMkV94s0vp765t2vwOt8EOxhWfmEP2v9myc2B95L5jJJZZgvCk2ojaV11CrFF4ookLng==
"@metamask/contract-metadata@^1.24.0":
version "1.25.0"
resolved "https://registry.yarnpkg.com/@metamask/contract-metadata/-/contract-metadata-1.25.0.tgz#442ace91fb40165310764b68d8096d0017bb0492"
integrity sha512-yhmYB9CQPv0dckNcPoWDcgtrdUp0OgK0uvkRE5QIBv4b3qENI1/03BztvK2ijbTuMlORUpjPq7/1MQDUPoRPVw==

"@metamask/controllers@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@metamask/controllers/-/controllers-8.0.0.tgz#42ac5aaef67a03d3fe599a67a36597e01902ca8d"
integrity sha512-TrteMifsCxV1g3WHcSD1X98fF4hKep3sXZNGfrvkPqa8mrF03hJke21WBSTRtvJ3vkNLRWgi+5I6lVXFTzbYuQ==
dependencies:
"@metamask/contract-metadata" "^1.23.0"
"@metamask/contract-metadata" "^1.24.0"
"@types/uuid" "^8.3.0"
async-mutex "^0.2.6"
babel-runtime "^6.26.0"
Expand Down

0 comments on commit 3957688

Please sign in to comment.