@colony/colony-js-contract-client
Version:
Method-like interface for Smart Contracts
542 lines (432 loc) • 17.2 kB
JavaScript
'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