@colony/purser-metamask
Version:
A javascript library to interact with a Metamask based Ethereum wallet
155 lines (143 loc) • 4.5 kB
Flow
/* @flow */
import Web3Instance from 'web3';
import { warning } from '@colony/purser-core/utils';
import MetamaskWallet from './class';
import {
methodCaller,
getInpageProvider,
detect as detectHelper,
setStateEventObserver,
} from './helpers';
import { staticMethods as messages } from './messages';
import type {
MetamaskInpageProviderType,
MetamaskStateEventsObserverType,
} from './flowtypes';
/**
* Open the Metamask Wallet instance
*
* @method open
*
* @return {WalletType} The wallet object resulted by instantiating the class
* (Object is wrapped in a promise).
*/
export const open = async (): Promise<MetamaskWallet> => {
let addressAfterEnable: string;
try {
/*
* We're on the Modern Metamask (after EIP-1102)
* See: https://eips.ethereum.org/EIPS/eip-1102
*/
if (global.ethereum) {
/*
* Inject the web3 provider
*/
global.web3 = new Web3Instance(global.ethereum);
/*
* Enable it
*/
[ addressAfterEnable ] = await global.ethereum.enable();
}
/*
* We're on the legacy version of Metamask
*/
if (!global.ethereum && global.web3) {
/*
* Warn the user about legacy mode
*
* @TODO Remove legacy metmask version messages
* After an adequate amount of time has passed
*/
warning(messages.legacyMode);
/*
* Enable it
*
* @NOTE There's an argument to be made here that it's dangerous to use
* the `getInpageProvider()` helper before using `detect()`
*/
const legacyProvider: MetamaskInpageProviderType = getInpageProvider();
legacyProvider.enable();
/*
* Inject the web3 provider (overwrite the current one)
*/
global.web3 = new Web3Instance(legacyProvider);
}
/*
* Everything functions the same since the Web3 instance is now in place
* (Granted, it's now using the 1.x.x version)
*/
return methodCaller(() => {
const {
publicConfigStore: { _state: state },
}: MetamaskInpageProviderType = getInpageProvider();
return new MetamaskWallet({
/*
* The EIP-1102 mode uses the address we got after enabling (and getting
* the users's permission), while the legacy mode get the address from
* the state
*/
address: addressAfterEnable || state.selectedAddress,
});
}, messages.metamaskNotAvailable);
} catch (caughtError) {
/*
* User did not authorize us to open his account. We cannot do anything else.
* (By clicking the 'Reject' button on the API request popup)
*/
throw new Error(messages.didNotAuthorize);
}
};
/**
* Check if Metamask's injected web3 proxy instance is available in the
* global object.
*
* Makes use of the `detect()` helper, basically it's a wrapper
* that exposes it from the module.
*
* @method detect
*
* @return {boolean} Only returns true if it's available, otherwise it will throw.
*/
export const detect = async (): Promise<boolean> => detectHelper();
/**
* Hook into Metamask's state events observers array to be able to act on account
* changes from the UI
*
* It's a wrapper around the `setStateEventObserver()` helper method
*
* @method accountChangeHook
*
* @param {Function} callback Function to add the state events update array
* It receives the state object as an only argument
*
* @return {Promise<void>} Does not return noting
*/
export const accountChangeHook =
async (callback: MetamaskStateEventsObserverType): Promise<void> => {
/*
* If detect fails, it will throw, with a message explaining the problem
* (Most likely Metamask will be locked, so we won't be able to get to
* the state observer via the in-page provider)
*/
detectHelper();
try {
return setStateEventObserver(callback);
} catch (error) {
/*
* If this throws/catches here it means something very weird is going on.
* `detect()` should catch anything that're directly related to Metamask's functionality,
* but if that passes and we have to catch it here, it means some underlying APIs
* might have changed, and this will be very hard to debug
*/
throw new Error(messages.cannotAddHook);
}
};
/*
* @NOTE There's an argument to be made here to expose the new version
*/
const metamaskWallet: Object = {
open,
detect,
accountChangeHook,
};
export default metamaskWallet;