UNPKG

@colony/purser-metamask

Version:

A javascript library to interact with a Metamask based Ethereum wallet

555 lines (474 loc) 19.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.verifyMessage = exports.verifyMessageCallback = exports.signMessage = exports.signMessageCallback = exports.signTransaction = exports.signTransactionCallback = exports.getTransaction = void 0; var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties")); var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator")); var _ethereumjsTx = require("ethereumjs-tx"); var _bn = _interopRequireDefault(require("bn.js")); var _awaitTransactionMined = require("await-transaction-mined"); var _utils = require("@colony/purser-core/utils"); var _validators = require("@colony/purser-core/validators"); var _normalizers = require("@colony/purser-core/normalizers"); var _helpers = require("@colony/purser-core/helpers"); var _defaults = require("@colony/purser-core/defaults"); var _helpers2 = require("./helpers"); var _methodLinks = require("./methodLinks"); var _defaults2 = require("./defaults"); var _messages = require("./messages"); /** * Get a transaction, with a workaround for some providers not returning * a pending transaction. * * If the transaction was not immediately returned, it's possible that * Infura is being used, and it isn't responding to `eth_getTransaction` * in the expected way (i.e. it isn't returning anything because the * transaction is not yet confirmed). * * This method uses a web3 0.20.x-compatible means of waiting for the * transaction to be confirmed (which will resolve to the receipt, * or reject if the transaction could not be confirmed. * * This can probably be removed when MetaMask has its own workaround. * See https://github.com/MetaMask/metamask-extension/issues/6704 */ var getTransaction = /*#__PURE__*/ function () { var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee(transactionHash) { var receiptPromise, transaction; return _regenerator.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: receiptPromise = (0, _awaitTransactionMined.awaitTx)(global.web3, transactionHash, { blocksToWait: 1 }); _context.next = 3; return (0, _methodLinks.getTransaction)(transactionHash); case 3: transaction = _context.sent; if (!transaction) { _context.next = 6; break; } return _context.abrupt("return", transaction); case 6: _context.next = 8; return receiptPromise; case 8: return _context.abrupt("return", (0, _methodLinks.getTransaction)(transactionHash)); case 9: case "end": return _context.stop(); } } }, _callee, this); })); return function getTransaction(_x) { return _ref.apply(this, arguments); }; }(); exports.getTransaction = getTransaction; var signTransactionCallback = function signTransactionCallback(chainId, resolve, reject) { return ( /*#__PURE__*/ function () { var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee2(error, transactionHash) { var normalizedTransactionHash, _ref3, gas, signedGasPrice, signedData, nonce, r, s, signedTo, v, signedValue, signedTransaction, serializedSignedTransaction, normalizedSignedTransaction; return _regenerator.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: _context2.prev = 0; if (!error) { _context2.next = 5; break; } if (!error.message.includes(_defaults2.STD_ERRORS.CANCEL_TX_SIGN)) { _context2.next = 4; break; } throw new Error(_messages.staticMethods.cancelTransactionSign); case 4: throw new Error(error.message); case 5: /* * Validate that the signature hash is in the correct format */ (0, _validators.hexSequenceValidator)(transactionHash); /* * Add the `0x` prefix to the signed transaction hash */ normalizedTransactionHash = (0, _normalizers.hexSequenceNormalizer)(transactionHash); /* * Get signed transaction object with transaction hash using Web3 * Include signature + any values MetaMask may have changed. */ _context2.next = 9; return getTransaction(normalizedTransactionHash); case 9: _ref3 = _context2.sent; gas = _ref3.gas; signedGasPrice = _ref3.gasPrice; signedData = _ref3.input; nonce = _ref3.nonce; r = _ref3.r; s = _ref3.s; signedTo = _ref3.to; v = _ref3.v; signedValue = _ref3.value; /* * RLP encode (to hex string) with ethereumjs-tx, prefix with * `0x` and return. Convert to BN all the numbers-as-strings. */ signedTransaction = new _ethereumjsTx.Transaction({ data: signedData, gasLimit: new _bn.default(gas), gasPrice: new _bn.default(signedGasPrice), nonce: new _bn.default(nonce), r: r, s: s, to: signedTo, v: v, value: new _bn.default(signedValue) }, (0, _helpers.getChainDefinition)(chainId)); serializedSignedTransaction = signedTransaction.serialize().toString(_defaults.HEX_HASH_TYPE); normalizedSignedTransaction = (0, _normalizers.hexSequenceNormalizer)(serializedSignedTransaction); return _context2.abrupt("return", resolve(normalizedSignedTransaction)); case 25: _context2.prev = 25; _context2.t0 = _context2["catch"](0); return _context2.abrupt("return", reject(_context2.t0)); case 28: case "end": return _context2.stop(); } } }, _callee2, this, [[0, 25]]); })); return function (_x2, _x3) { return _ref2.apply(this, arguments); }; }() ); }; /** * Sign (and send) a transaction object and return the serialized signature (as a hex string) * * @TODO Refactor to only sign the transaction * This is only after Metamask will allow us that functionality (see below) * * Metamask doesn't currently allow us to sign a transaction without also broadcasting it to * the network. See this issue for context: * https://github.com/MetaMask/metamask-extension/issues/3475 * * @method signTransaction * * @param {string} from the sender address (provided by the Wallet instance) * @param {bigNumber} gasPrice gas price for the transaction in WEI (as an instance of bigNumber), defaults to 9000000000 (9 GWEI) * @param {bigNumber} gasLimit gas limit for the transaction (as an instance of bigNumber), defaults to 21000 * @param {number} nonce the nonce to use for the transaction (as a number) * @param {string} to the address to which to the transaction is sent * @param {bigNumber} value the value of the transaction in WEI (as an instance of bigNumber), defaults to 1 * @param {string} inputData data appended to the transaction (as a `hex` string) * * All the above params are sent in as props of an object. * * @return {Promise<string>} the hex signature string */ exports.signTransactionCallback = signTransactionCallback; var signTransaction = /*#__PURE__*/ function () { var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee3() { var _ref5, from, manualNonce, transactionObject, _transactionObjectVal, chainId, gasPrice, gasLimit, to, value, inputData, _args3 = arguments; return _regenerator.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: _ref5 = _args3.length > 0 && _args3[0] !== undefined ? _args3[0] : {}, from = _ref5.from, manualNonce = _ref5.nonce, transactionObject = (0, _objectWithoutProperties2.default)(_ref5, ["from", "nonce"]); _transactionObjectVal = (0, _helpers.transactionObjectValidator)(transactionObject), chainId = _transactionObjectVal.chainId, gasPrice = _transactionObjectVal.gasPrice, gasLimit = _transactionObjectVal.gasLimit, to = _transactionObjectVal.to, value = _transactionObjectVal.value, inputData = _transactionObjectVal.inputData; (0, _validators.addressValidator)(from); /* * Metamask auto-sets the nonce based on the next one available. You can manually * override it, but it's best to omit it. * * So we only validate if there is one, otherwise we just pass undefined * to the transaction object. * * We also notify (in dev mode) the user about not setting the nonce. */ if (manualNonce) { (0, _validators.safeIntegerValidator)(manualNonce); (0, _utils.warning)(_messages.staticMethods.dontSetNonce); } /* * 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. */ return _context3.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, reject) { return (0, _methodLinks.signTransaction)(Object.assign({}, { from: (0, _normalizers.addressNormalizer)(from), /* * We don't need to normalize these three values since Metamask accepts * number values directly, so we don't need to convert them to hex */ value: value.toString(), gas: gasLimit.toString(), gasPrice: gasPrice.toString(), data: (0, _normalizers.hexSequenceNormalizer)(inputData), chainId: chainId, /* * Most likely this value is `undefined`, but that is good (see above) */ nonce: manualNonce }, /* * Only send (and normalize) the destination address if one was * provided in the initial transaction object. */ to ? { to: (0, _normalizers.addressNormalizer)(to) } : {}), signTransactionCallback(chainId, resolve, reject)); }); }, _messages.staticMethods.cannotSendTransaction)); case 5: case "end": return _context3.stop(); } } }, _callee3, this); })); return function signTransaction() { return _ref4.apply(this, arguments); }; }(); exports.signTransaction = signTransaction; var signMessageCallback = function signMessageCallback(resolve, reject) { return function (error, messageSignature) { try { if (error) { /* * If the user cancels signing the message we still throw, * but we customize the message */ if (error.message.includes(_defaults2.STD_ERRORS.CANCEL_MSG_SIGN)) { throw new Error(_messages.staticMethods.cancelMessageSign); } throw new Error(error.message); } /* * Validate that the signature is in the correct format */ (0, _validators.hexSequenceValidator)(messageSignature); /* * Add the `0x` prefix to the message's signature */ var normalizedSignature = (0, _normalizers.hexSequenceNormalizer)(messageSignature); return resolve(normalizedSignature); } catch (caughtError) { return reject(caughtError); } }; }; /** * Sign a message and return the signature. Useful for verifying identities. * * @method signMessage * * @param {string} currentAddress The current selected address (in the UI) * @param {string} message the message you want to sign * @param {any} messageData the message data (hex string or UInt8Array) you want to sign * * All the above params are sent in as props of an {object. * * @return {Promise<string>} The signed message `hex` string (wrapped inside a `Promise`) */ exports.signMessageCallback = signMessageCallback; var signMessage = /*#__PURE__*/ function () { var _ref6 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee4() { var _ref7, currentAddress, message, messageData, toSign, _args4 = arguments; return _regenerator.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: _ref7 = _args4.length > 0 && _args4[0] !== undefined ? _args4[0] : {}, currentAddress = _ref7.currentAddress, message = _ref7.message, messageData = _ref7.messageData; (0, _validators.addressValidator)(currentAddress); toSign = (0, _helpers.messageOrDataValidator)({ message: message, messageData: messageData }); /* * 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. */ 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, reject) { /* * 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(toSign).toString(_defaults.HEX_HASH_TYPE)), currentAddress, signMessageCallback(resolve, reject)); }); }, _messages.staticMethods.cannotSignMessage)); case 4: case "end": return _context4.stop(); } } }, _callee4, this); })); return function signMessage() { return _ref6.apply(this, arguments); }; }(); exports.signMessage = signMessage; var verifyMessageCallback = function verifyMessageCallback(currentAddress, resolve, reject) { return function (error, recoveredAddress) { try { if (error) { throw new Error(error.message); } /* * Validate that the recovered address is correct */ (0, _validators.addressValidator)(recoveredAddress); /* * Add the `0x` prefix to the recovered address */ var normalizedRecoveredAddress = (0, _normalizers.addressNormalizer)(recoveredAddress); /* * Add the `0x` prefix to the current address */ var normalizedCurrentAddress = (0, _normalizers.addressNormalizer)(currentAddress); return resolve(normalizedRecoveredAddress === normalizedCurrentAddress); } catch (caughtError) { return reject(caughtError); } }; }; /** * Verify a signed message. Useful for verifying identity. (In conjunction with `signMessage`) * * @method verifyMessage * * @param {string} message The message to verify if it was signed correctly * @param {string} signature The message signature as a `hex` string (you usually get this via `signMessage`) * @param {string} currentAddress The current selected address (in the UI) * * All the above params are sent in as props of an object. * * @return {Promise<boolean>} A boolean to indicate if the message/signature pair are valid (wrapped inside a `Promise`) */ exports.verifyMessageCallback = verifyMessageCallback; var verifyMessage = /*#__PURE__*/ function () { var _ref8 = (0, _asyncToGenerator2.default)( /*#__PURE__*/ _regenerator.default.mark(function _callee5() { var _ref9, currentAddress, messageVerificationObject, _messageVerificationO, message, signature, _args5 = arguments; return _regenerator.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: _ref9 = _args5.length > 0 && _args5[0] !== undefined ? _args5[0] : {}, currentAddress = _ref9.currentAddress, messageVerificationObject = (0, _objectWithoutProperties2.default)(_ref9, ["currentAddress"]); /* * Validate the current address */ (0, _validators.addressValidator)(currentAddress); /* * Validate the rest of the pros using the core helper */ _messageVerificationO = (0, _helpers.messageVerificationObjectValidator)(messageVerificationObject), message = _messageVerificationO.message, signature = _messageVerificationO.signature; /* * 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. */ return _context5.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, reject) { /* * Verify the message */ (0, _methodLinks.verifyMessage)(message, /* * Ensure the signature has the `0x` prefix */ (0, _normalizers.hexSequenceNormalizer)(signature), verifyMessageCallback(currentAddress, resolve, reject)); }); }, _messages.staticMethods.cannotSignMessage)); case 4: case "end": return _context5.stop(); } } }, _callee5, this); })); return function verifyMessage() { return _ref8.apply(this, arguments); }; }(); exports.verifyMessage = verifyMessage;