UNPKG

@colony/purser-metamask

Version:

A javascript library to interact with a Metamask based Ethereum wallet

396 lines (346 loc) 13.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread")); var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _lodash = _interopRequireDefault(require("lodash.isequal")); var _utils = require("@colony/purser-core/utils"); var _helpers = require("@colony/purser-core/helpers"); var _validators = require("@colony/purser-core/validators"); var _normalizers = require("@colony/purser-core/normalizers"); var _defaults = require("@colony/purser-core/defaults"); var _types = require("@colony/purser-core/types"); var _staticMethods = require("./staticMethods"); var _helpers2 = require("./helpers"); var _validators2 = require("./validators"); var _methodLinks = require("./methodLinks"); var _defaults2 = require("./defaults"); var _messages = require("./messages"); var SETTERS = _defaults.DESCRIPTORS.SETTERS, GETTERS = _defaults.DESCRIPTORS.GETTERS, GENERIC_PROPS = _defaults.DESCRIPTORS.GENERIC_PROPS, WALLET_PROPS = _defaults.DESCRIPTORS.WALLET_PROPS; /* * "Private" (internal) variable(s). */ var state = {}; var internalPublicKey; var MetamaskWallet = /*#__PURE__*/ function () { /* * `publicKey` prop is a getter */ /* * @TODO Add specific Flow type * * See the core generic wallet for this, since that will implement them. * This will just use the ones declared there. */ function MetamaskWallet(_ref) { var _this = this; var address = _ref.address; (0, _classCallCheck2.default)(this, MetamaskWallet); /* * Validate the address that's coming in from Metamask */ (0, _validators.addressValidator)(address); Object.defineProperties(this, { /* * The initial address is set when `open()`-ing the wallet, but after that * it's updated via the Metamask state change observer. * * This way, we keep it in sync with the changes from Metamask's UI */ address: Object.assign({}, { value: address }, SETTERS), type: Object.assign({}, { value: _types.TYPE_SOFTWARE }, GENERIC_PROPS), subtype: Object.assign({}, { value: _types.SUBTYPE_METAMASK }, GENERIC_PROPS), sign: Object.assign({}, { value: function () { var _value = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee(transactionObject) { return _regenerator.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: /* * Validate the trasaction's object input */ (0, _helpers.userInputValidator)({ firstArgument: transactionObject }); return _context.abrupt("return", (0, _staticMethods.signTransaction)(Object.assign({}, transactionObject, { from: _this.address }))); case 2: case "end": return _context.stop(); } } }, _callee, this); })); return function value(_x) { return _value.apply(this, arguments); }; }() }, WALLET_PROPS), signMessage: Object.assign({}, { value: function () { var _value2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee2() { var messageObject, _args2 = arguments; return _regenerator.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: messageObject = _args2.length > 0 && _args2[0] !== undefined ? _args2[0] : {}; /* * Validate the trasaction's object input */ (0, _helpers.userInputValidator)({ firstArgument: messageObject, requiredOr: _defaults.REQUIRED_PROPS.SIGN_MESSAGE }); return _context2.abrupt("return", (0, _staticMethods.signMessage)({ currentAddress: _this.address, message: messageObject.message, messageData: messageObject.messageData })); case 3: case "end": return _context2.stop(); } } }, _callee2, this); })); return function value() { return _value2.apply(this, arguments); }; }() }, WALLET_PROPS), verifyMessage: Object.assign({}, { value: function () { var _value3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee3(messageVerificationObject) { return _regenerator.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: /* * Validate the trasaction's object input */ (0, _helpers.userInputValidator)({ firstArgument: messageVerificationObject, requiredAll: _defaults.REQUIRED_PROPS.VERIFY_MESSAGE }); return _context3.abrupt("return", (0, _staticMethods.verifyMessage)((0, _objectSpread2.default)({ currentAddress: _this.address }, messageVerificationObject))); case 2: case "end": return _context3.stop(); } } }, _callee3, this); })); return function value(_x2) { return _value3.apply(this, arguments); }; }() }, WALLET_PROPS) }); /* * We must check for the Metamask injected in-page proxy every time we * try to access it. This is because something can change it from the time * of last detection until now. * * So we must ensure, again, that we have a state update event to hook * our update method onto. */ (0, _helpers2.methodCaller)( /* * @TODO Move into own (non-anonymous) method * This way we could better test it * * Set the state change observer * * This tracks updates Metamask's states and updates the local address * value if that changes in the UI */ function () { return (0, _helpers2.setStateEventObserver)( /* * @TODO Move into own (non-anonymous) method * This way we could better test it */ function (newState) { try { /* * Validate the state object that's coming in. * It should have all the props needed for us to work with. * * If they aren't there, it means that either Metamask is locked, * or somebody tampered with them. */ (0, _validators2.validateMetamaskState)(newState); /* * We only update the values if the state has changed. * (We're using lodash here to deep compare the two state objects) */ if (!(0, _lodash.default)(state, newState)) { state = newState; _this.address = newState.selectedAddress; /* * Reset the saved public key, as the address now changed */ internalPublicKey = undefined; return true; } return false; } catch (caughtError) { /* * We don't want to throw or stop execution, so in the case that the * state doesn't validate, and update and silently return `false`. */ return false; } }); }, _messages.MetamaskWallet.cannotObserve); } /* * Public Key Getter */ /* eslint-disable-next-line class-methods-use-this */ (0, _createClass2.default)(MetamaskWallet, [{ key: "publicKey", get: function get() { /* * We can't memoize the getter (as we do in most other such getters) * * This is because the address could change at any time leaving us with a * stale value for the public key, as there is no way (currently) to invalidate * this value. */ if (internalPublicKey) { return Promise.resolve(internalPublicKey); } return MetamaskWallet.recoverPublicKey(this.address); } /** * Recover the public key from a signed message. * Sign a message, and use that signature to recover the (R), (S) signature * components, along with the reco(V)ery param. We then use those values to * recover, set internally, and return the public key. * * @method recoverPublicKey * * @param {string} currentAddress The current selected address. * Note the we don't need to validate this here since it comes from a trusted * source: the class constructor. * * @return {Promise} The recovered public key (for the currently selected addresss) */ }], [{ key: "recoverPublicKey", value: function () { var _recoverPublicKey = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee4(currentAddress) { return _regenerator.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: return _context4.abrupt("return", (0, _helpers2.methodCaller)( /* * @TODO Move into own (non-anonymous) method * This way we could better test it */ function () { return new Promise(function (resolve) { /* * Sign the message. This will prompt the user via Metamask's UI */ (0, _methodLinks.signMessage)( /* * Ensure the hex string has the `0x` prefix */ (0, _normalizers.hexSequenceNormalizer)( /* * We could really do with default Flow types for Buffer... */ /* $FlowFixMe */ Buffer.from(_defaults2.PUBLICKEY_RECOVERY_MESSAGE).toString(_defaults.HEX_HASH_TYPE)), currentAddress, /* * @TODO Move into own (non-anonymous) method * This way we could better test it */ function (error, signature) { try { /* * Validate that the signature is in the correct format */ (0, _validators.hexSequenceValidator)(signature); var recoveredPublicKey = (0, _helpers.recoverPublicKey)({ message: _defaults2.PUBLICKEY_RECOVERY_MESSAGE, signature: signature }); /* * Add the `0x` prefix to the recovered public key */ var normalizedPublicKey = (0, _normalizers.hexSequenceNormalizer)(recoveredPublicKey); /* * Also set the internal public key */ internalPublicKey = normalizedPublicKey; return resolve(normalizedPublicKey); } catch (caughtError) { /* * Don't throw an Error if the user just cancels signing the message. * This is normal UX, not an exception */ if (error.message.includes(_defaults2.STD_ERRORS.CANCEL_MSG_SIGN)) { return (0, _utils.warning)(_messages.staticMethods.cancelMessageSign); } throw new Error(error.message); } }); }); }, _messages.MetamaskWallet.cannotGetPublicKey)); case 1: case "end": return _context4.stop(); } } }, _callee4, this); })); return function recoverPublicKey(_x3) { return _recoverPublicKey.apply(this, arguments); }; }() }]); return MetamaskWallet; }(); /* * We need to use `defineProperties` to make props enumerable. * When adding them via a `Class` getter/setter it will prevent that by default */ exports.default = MetamaskWallet; Object.defineProperties(MetamaskWallet.prototype, { publicKey: GETTERS });