UNPKG

@colony/colony-js-contract-client

Version:

Method-like interface for Smart Contracts

542 lines (432 loc) 17.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray'); var _slicedToArray3 = _interopRequireDefault(_slicedToArray2); var _defineProperty2 = require('babel-runtime/helpers/defineProperty'); var _defineProperty3 = _interopRequireDefault(_defineProperty2); var _extends3 = require('babel-runtime/helpers/extends'); var _extends4 = _interopRequireDefault(_extends3); var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); var _regenerator = require('babel-runtime/regenerator'); var _regenerator2 = _interopRequireDefault(_regenerator); var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _lodash = require('lodash.flatmap'); var _lodash2 = _interopRequireDefault(_lodash); var _ContractEvent = require('./ContractEvent'); var _ContractEvent2 = _interopRequireDefault(_ContractEvent); var _ContractMethod = require('./ContractMethod'); var _ContractMethod2 = _interopRequireDefault(_ContractMethod); var _ContractMethodCaller = require('./ContractMethodCaller'); var _ContractMethodCaller2 = _interopRequireDefault(_ContractMethodCaller); var _ContractMethodSender = require('./ContractMethodSender'); var _ContractMethodSender2 = _interopRequireDefault(_ContractMethodSender); var _ContractMethodMultisigSender = require('./ContractMethodMultisigSender'); var _ContractMethodMultisigSender2 = _interopRequireDefault(_ContractMethodMultisigSender); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* eslint-disable import/no-cycle */ var ContractClient = function () { (0, _createClass3.default)(ContractClient, null, [{ key: 'Caller', // Static getters used in lieu of named exports; this package only has // one export. // Mapping of event topics to ContractEvents // The contract loading query the class was constructed with // The adapter used to communicate with the blockchain get: function get() { return _ContractMethodCaller2.default; } // Show additional logs // The contract event subscription methods // The contract interface (as provided by the adapter) }, { key: 'Sender', get: function get() { return _ContractMethodSender2.default; } }, { key: 'MultisigSender', get: function get() { return _ContractMethodMultisigSender2.default; } }, { key: 'Event', get: function get() { return _ContractEvent2.default; } }, { key: 'defaultQuery', get: function get() { // eslint-disable-next-line no-console console.warn('No query defined; defaultQuery is designed to be ' + 'defined in a derived class'); return {}; } }]); function ContractClient(_ref) { var adapter = _ref.adapter, query = _ref.query, verbose = _ref.verbose; (0, _classCallCheck3.default)(this, ContractClient); this.events = {}; this.eventSignatures = {}; this.adapter = adapter; this._query = Object.assign({}, this.constructor.defaultQuery, query); this.verbose = verbose; } (0, _createClass3.default)(ContractClient, [{ key: 'init', value: function () { var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() { return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!this._contract) { _context.next = 2; break; } throw new Error('ContractClient already initialized'); case 2: _context.next = 4; return this.adapter.getContract(this._query); case 4: this._contract = _context.sent; this.initializeContractMethods(); return _context.abrupt('return', this); case 7: case 'end': return _context.stop(); } } }, _callee, this); })); function init() { return _ref2.apply(this, arguments); } return init; }() /** * Designed to be overridden in a derived class; called in the constructor. */ // eslint-disable-next-line class-methods-use-this }, { key: 'initializeContractMethods', value: function initializeContractMethods() {} /** * Low-level method to call a named contract function with an array of * arguments that have been properly validated for that function. */ }, { key: 'call', value: function () { var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(functionName, args) { return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: return _context2.abrupt('return', this.contract.callConstant(functionName, args)); case 1: case 'end': return _context2.stop(); } } }, _callee2, this); })); function call(_x, _x2) { return _ref3.apply(this, arguments); } return call; }() }, { key: 'callTransaction', value: function () { var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(transaction) { return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: return _context3.abrupt('return', this.adapter.callTransaction(transaction)); case 1: case 'end': return _context3.stop(); } } }, _callee3, this); })); function callTransaction(_x3) { return _ref4.apply(this, arguments); } return callTransaction; }() /** * Low-level method to estimate the gas cost of sending a transaction to * call a contract function with an array of arguments that have been * properly validated for that function. */ }, { key: 'estimate', value: function () { var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(functionName, args) { return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: return _context4.abrupt('return', this.contract.callEstimate(functionName, args)); case 1: case 'end': return _context4.stop(); } } }, _callee4, this); })); function estimate(_x4, _x5) { return _ref5.apply(this, arguments); } return estimate; }() /** * Low-level method to send a transaction to a named contract function * with an array of arguments that have been properly validated for that * function, and optional transaction options. */ }, { key: 'send', value: function () { var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(functionName, args, options) { return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: return _context5.abrupt('return', this.contract.sendTransaction(functionName, args, options)); case 1: case 'end': return _context5.stop(); } } }, _callee5, this); })); function send(_x6, _x7, _x8) { return _ref6.apply(this, arguments); } return send; }() /** * Get logs with filter, and return parsed event logs. */ }, { key: 'getEvents', value: function () { var _ref7 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee6() { var filter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var logs; return _regenerator2.default.wrap(function _callee6$(_context6) { while (1) { switch (_context6.prev = _context6.next) { case 0: _context6.next = 2; return this.getLogs(filter); case 2: logs = _context6.sent; return _context6.abrupt('return', this.parseLogs(logs)); case 4: case 'end': return _context6.stop(); } } }, _callee6, this); })); function getEvents() { return _ref7.apply(this, arguments); } return getEvents; }() /** * Get logs from the contract with filter. * * The filter `topics` is an array, where each element is a string, array of * strings, or null. In each position, these represent a filter which matches * events including that single topic, one of any of the array of topics, or * any topic in that position respectively. * * - String: match only this topic in this position * - Array: match any of these topics in this position * - Null: match any topic in this position * * The returned logs will match these filters in each position. Trailing null * values will require a topic in that position (e.g. [null, null] will only * match logs with at least two topics). */ }, { key: 'getLogs', value: function () { var _ref8 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee7() { var _this = this; var filter = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var _filter$eventNames, eventNames, _filter$topics, topics, extraTopics, _topics$; return _regenerator2.default.wrap(function _callee7$(_context7) { while (1) { switch (_context7.prev = _context7.next) { case 0: _filter$eventNames = filter.eventNames, eventNames = _filter$eventNames === undefined ? [] : _filter$eventNames, _filter$topics = filter.topics, topics = _filter$topics === undefined ? [] : _filter$topics; // Get topics for the given eventNames extraTopics = (0, _lodash2.default)(eventNames, function (eventName) { if (!_this.events[eventName]) throw new Error('Cannot get logs for unknown event'); return _this.events[eventName].interface.topics; }); // Combine any existing topics with the extra ones if (!topics.length) { topics.push(extraTopics); } else if (Array.isArray(topics[0])) { (_topics$ = topics[0]).push.apply(_topics$, (0, _toConsumableArray3.default)(extraTopics)); } else { topics[0] = [topics[0]].concat((0, _toConsumableArray3.default)(extraTopics)); } // Fetch the logs and parse return _context7.abrupt('return', this.adapter.provider.getLogs((0, _extends4.default)({}, filter, { topics: topics }))); case 4: case 'end': return _context7.stop(); } } }, _callee7, this); })); function getLogs() { return _ref8.apply(this, arguments); } return getLogs; }() /** * Given a transaction receipt, decode the event logs with the contract * interface, then use the corresponding ContractEvents to collect event data. */ }, { key: 'getReceiptEventData', value: function getReceiptEventData(_ref9) { var _this2 = this; var _ref9$logs = _ref9.logs, logs = _ref9$logs === undefined ? [] : _ref9$logs; var events = this.contract.interface.events; var eventNames = Object.keys(events); return logs // Find matching event info by the topic .map(function (log) { var eventName = eventNames // Filter out events not supported by the interface (e.g. from BYOT) .filter(function (name) { return !!events[name]; }).find(function (name) { return ( // The first topic should be the hashed event signature log.topics.includes(events[name].topics[0]) ); }); return eventName ? (0, _extends4.default)({}, log, { eventInfo: events[eventName] }) : null; }) // Filter out logs we couldn't find on the interface as events .filter(Boolean) // Parse the event data and add it to a resulting object .reduce(function (acc, _ref10) { var eventInfo = _ref10.eventInfo, topics = _ref10.topics, data = _ref10.data; var args = eventInfo.parse(topics, data); var event = _this2.events[eventInfo.name]; if (!event) throw new Error('Event ' + eventInfo.name + ' not found'); // Add the event data both at the top level and under the event name var eventData = event.parse(args); return Object.assign(acc, (0, _extends4.default)({}, eventData, (0, _defineProperty3.default)({}, eventInfo.name, eventData))); }, {}); } }, { key: 'createTransactionData', value: function createTransactionData(functionName, args) { return this.contract.createTransactionData(functionName, args); } }, { key: 'parseLogs', value: function parseLogs(logs) { var _this3 = this; return logs.filter(function (_ref11) { var _ref11$topics = (0, _slicedToArray3.default)(_ref11.topics, 1), topic = _ref11$topics[0]; return _this3.eventSignatures[topic]; }).map(function (log) { return (0, _extends4.default)({}, _this3.eventSignatures[log.topics[0]].parseLog(log), { eventName: _this3.eventSignatures[log.topics[0]].eventName }); }); } }, { key: 'addMethod', value: function addMethod(Method, name, def) { if (Reflect.has(this, name)) throw new Error('A ContractMethod named "' + name + '" already exists'); Object.assign(this, (0, _defineProperty3.default)({}, name, new Method((0, _extends4.default)({ name: name, functionName: name, client: this }, def)))); } }, { key: 'addCaller', value: function addCaller(name, def) { this.addMethod(this.constructor.Caller, name, def); } }, { key: 'addSender', value: function addSender(name, def) { this.addMethod(this.constructor.Sender, name, def); } }, { key: 'addMultisigSender', value: function addMultisigSender(name, def) { this.addMethod(this.constructor.MultisigSender, name, def); } /** * Add event subscription functionality for a particular event of this * contract to the given ContractClient instance. */ }, { key: 'addEvent', value: function addEvent(eventName, argsDef) { if (Reflect.has(this.events, eventName)) { throw new Error('An event named "' + eventName + '" already exists'); } // Allow initialising of clients where some events may be missing in the // ABI, due to changing of events on the contract and then log the error // as a warning if the client is initialized in verbose mode. try { var event = new _ContractEvent2.default({ eventName: eventName, client: this, argsDef: argsDef }); Object.assign(this.events, (0, _defineProperty3.default)({}, eventName, event)); Object.assign(this.eventSignatures, (0, _defineProperty3.default)({}, event.interface.topics[0], event)); } catch (error) { if (this.verbose) { console.warn('WARNING: ' + error.message); } } } }, { key: 'contract', get: function get() { if (!this._contract) throw new Error('Contract not loaded; did you forget to call `.init()`?'); return this._contract; } }, { key: 'network', get: function get() { // eslint-disable-next-line no-underscore-dangle return this.adapter.loader._network || this._query.network; } }]); return ContractClient; }(); exports.default = ContractClient; //# sourceMappingURL=ContractClient.js.map