Skip to content

Commit

Permalink
Replace controller context
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Gudahtt committed Apr 26, 2021
1 parent 57ba915 commit 65d6a72
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 92 deletions.
191 changes: 119 additions & 72 deletions app/core/Engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,76 +65,128 @@ 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),
initialIdentities: initialState.preferencesController?.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
})
];

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 +214,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 +233,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 +294,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 +360,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 +393,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 +481,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 65d6a72

Please sign in to comment.