UNPKG

@colony/purser-software

Version:

A javascript library to interact with a software Ethereum wallet, based on the ethers.js library

491 lines (432 loc) 17.1 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; 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 _secretStorage = require("ethers/utils/secret-storage"); var _ethereumjsUtil = require("ethereumjs-util"); var _helpers = require("@colony/purser-core/helpers"); var _utils = require("@colony/purser-core/utils"); var _normalizers = require("@colony/purser-core/normalizers"); var _validators = require("@colony/purser-core/validators"); var _defaults = require("@colony/purser-core/defaults"); var _types = require("@colony/purser-core/types"); var _staticMethods = require("./staticMethods"); var _messages = require("./messages"); var GETTERS = _defaults.DESCRIPTORS.GETTERS, WALLET_PROPS = _defaults.DESCRIPTORS.WALLET_PROPS; /* * "Private" (internal) variable(s) */ var internalKeystoreJson; var internalEncryptionPassword; /** * @NOTE We're no longer directly extending the Ethers Wallet Class * * This is due to the fact that we need more control over the resulting Class * object (SoftwareWallet in this case). * * We're still shadowing the Ethers Wallet, meaning when opening or creating a new * wallet, we will first create a Ethers Wallet instance than pass that along * to the SoftwareWallet constructor. * * This way we don't have to deal with non-configurable or non-writable props, * or the providers being baked in. */ var SoftwareWallet = /*#__PURE__*/ function () { /* * @TODO Add specific Flow types * * For the three main wallet methods */ function SoftwareWallet() { var _this = this; var ethersInstance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; (0, _classCallCheck2.default)(this, SoftwareWallet); var address = ethersInstance.address, privateKey = ethersInstance.privateKey, password = ethersInstance.password, mnemonic = ethersInstance.originalMnemonic, keystore = ethersInstance.keystore, chainId = ethersInstance.chainId, ethersSign = ethersInstance.sign, ethersSignMessage = ethersInstance.signMessage; /* * Validate the private key and address that's coming in from ethers. */ (0, _validators.addressValidator)(address); (0, _validators.hexSequenceValidator)(privateKey); /* * If we have a keystore JSON string and encryption password, set them * to the internal variables. */ internalEncryptionPassword = password; internalKeystoreJson = keystore; /* * Set the private key to a "internal" variable since we only allow * access to it through a getter and not directly via a prop. */ Object.defineProperties(this, { address: Object.assign({}, { value: address }, WALLET_PROPS), type: Object.assign({}, { value: _types.TYPE_SOFTWARE }, WALLET_PROPS), subtype: Object.assign({}, { value: _types.SUBTYPE_ETHERS }, WALLET_PROPS), chainId: Object.assign({}, { value: chainId }, WALLET_PROPS), /* * Getters */ privateKey: Object.assign({}, { get: function () { var _get = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee() { return _regenerator.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: return _context.abrupt("return", privateKey); case 1: case "end": return _context.stop(); } } }, _callee, this); })); return function get() { return _get.apply(this, arguments); }; }() }, GETTERS), /* * @TODO Allow users control of the derivation path * When instantiating a new class instance. But this is only if the feature * turns out to be required. */ derivationPath: Object.assign({}, { get: function () { var _get2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee2() { return _regenerator.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: return _context2.abrupt("return", (0, _helpers.derivationPathSerializer)({ change: _defaults.PATH.CHANGE, addressIndex: _defaults.PATH.INDEX })); case 1: case "end": return _context2.stop(); } } }, _callee2, this); })); return function get() { return _get2.apply(this, arguments); }; }() }, GETTERS), sign: Object.assign({}, { value: function () { var _value = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee3(transactionObject) { var _ref, _ref$chainId, transactionChainId; 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: transactionObject }); _ref = transactionObject || {}, _ref$chainId = _ref.chainId, transactionChainId = _ref$chainId === void 0 ? _this.chainId : _ref$chainId; return _context3.abrupt("return", (0, _staticMethods.signTransaction)(Object.assign({}, transactionObject, { chainId: transactionChainId, /* * @NOTE We need to bind the whole ethers instance * * Since the `sign` will look for different methods inside the * class's prototype, and if it fails to find them, it will * crash */ callback: ethersSign.bind(ethersInstance) }))); case 3: case "end": return _context3.stop(); } } }, _callee3, 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 _callee4() { var messageObject, _args4 = arguments; return _regenerator.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: messageObject = _args4.length > 0 && _args4[0] !== undefined ? _args4[0] : {}; /* * Validate the trasaction's object input */ (0, _helpers.userInputValidator)({ firstArgument: messageObject, requiredOr: _defaults.REQUIRED_PROPS.SIGN_MESSAGE }); return _context4.abrupt("return", (0, _staticMethods.signMessage)({ message: messageObject.message, messageData: messageObject.messageData, /* * @NOTE We need to bind the whole ethers instance * * Since the `signMessage` will look for different methods inside the * class's prototype, and if it fails to find them, it will * crash */ callback: ethersSignMessage.bind(ethersInstance) })); case 3: case "end": return _context4.stop(); } } }, _callee4, 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 _callee5() { var signatureVerificationObject, message, signature, _args5 = arguments; return _regenerator.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: signatureVerificationObject = _args5.length > 0 && _args5[0] !== undefined ? _args5[0] : {}; /* * Validate the trasaction's object input */ (0, _helpers.userInputValidator)({ firstArgument: signatureVerificationObject, requiredAll: _defaults.REQUIRED_PROPS.VERIFY_MESSAGE }); message = signatureVerificationObject.message, signature = signatureVerificationObject.signature; return _context5.abrupt("return", (0, _staticMethods.verifyMessage)({ address: address, message: message, signature: signature })); case 4: case "end": return _context5.stop(); } } }, _callee5, this); })); return function value() { return _value3.apply(this, arguments); }; }() }, WALLET_PROPS) }); /* * Only set the `mnemonic` prop if it's available, so it won't show up * as being defined, but set to `undefined` */ if (mnemonic) { Object.defineProperty(this, 'mnemonic', Object.assign({}, { get: function () { var _get3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee6() { return _regenerator.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: return _context6.abrupt("return", mnemonic); case 1: case "end": return _context6.stop(); } } }, _callee6, this); })); return function get() { return _get3.apply(this, arguments); }; }() }, GETTERS)); } } (0, _createClass2.default)(SoftwareWallet, [{ key: "keystore", get: function get() { var _this2 = this; /* * We're wrapping the getter (returning actually) in a IIFE so we can * write it using a `async` pattern. */ return (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee7() { var privateKey; return _regenerator.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: if (!internalEncryptionPassword) { _context7.next = 6; break; } _context7.next = 3; return _this2.privateKey; case 3: privateKey = _context7.sent; /* * Memoizing the getter * * This is quite an expensive operation, so we're memoizing it that * on the next call (an the others after that) it won't re-calculate * the value again. */ Object.defineProperty(_this2, 'keystore', Object.assign({}, GETTERS, { value: internalKeystoreJson && Promise.resolve(internalKeystoreJson) || /* * We're usign Ethers's direct secret storage encrypt method to generate * the keystore JSON string * * @TODO Validate the password * * The password won't work if it's not a string, so it will be best if * we write a string validator for it */ (0, _secretStorage.encrypt)(privateKey, internalEncryptionPassword.toString()) })); return _context7.abrupt("return", internalKeystoreJson && Promise.resolve(internalKeystoreJson) || /* * We're usign Ethers's direct secret storage encrypt method to generate * the keystore JSON string * * @TODO Validate the password * * The password won't work if it's not a string, so it will be best if * we write a string validator for it */ (0, _secretStorage.encrypt)(privateKey, internalEncryptionPassword.toString())); case 6: (0, _utils.warning)(_messages.walletClass.noPassword); return _context7.abrupt("return", Promise.reject()); case 8: case "end": return _context7.stop(); } } }, _callee7, this); }))(); } /* * Just set the encryption password, we don't return anything from here, * hence we don't have a need for `this`. * * This is just an convenince to allow us to set the encryption password * after the wallet has be created / instantiated. */ /* eslint-disable-next-line class-methods-use-this */ , set: function set(newEncryptionPassword) { internalEncryptionPassword = newEncryptionPassword; } }, { key: "publicKey", get: function get() { var _this3 = this; /* * We're wrapping the getter (returning actually) in a IIFE so we can * write it using a `async` pattern. */ return (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee8() { var privateKey, reversedPublicKey, normalizedPublicKey; return _regenerator.default.wrap(function _callee8$(_context8) { while (1) { switch (_context8.prev = _context8.next) { case 0: _context8.next = 2; return _this3.privateKey; case 2: privateKey = _context8.sent; reversedPublicKey = (0, _ethereumjsUtil.privateToPublic)(privateKey).toString(_defaults.HEX_HASH_TYPE); /* * Validate the reversed public key */ (0, _validators.hexSequenceValidator)(reversedPublicKey); /* * Then normalize it to ensure it has the `0x` prefix */ normalizedPublicKey = (0, _normalizers.hexSequenceNormalizer)(reversedPublicKey); /* * Memoizing the getter * * While this is not an expensive operation, it's still a good idea * to memoize it so it returns a tiny bit faster. */ Object.defineProperty(_this3, 'publicKey', Object.assign({}, GETTERS, { value: Promise.resolve(normalizedPublicKey) })); return _context8.abrupt("return", normalizedPublicKey); case 8: case "end": return _context8.stop(); } } }, _callee8, this); }))(); } }]); return SoftwareWallet; }(); /* * 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 = SoftwareWallet; Object.defineProperties(SoftwareWallet.prototype, { publicKey: GETTERS, keystore: GETTERS });