UNPKG

@parity/api

Version:

The Parity Promise-based API library for interfacing with Ethereum over RPC

507 lines (506 loc) 21.2 kB
"use strict"; // Copyright 2015-2019 Parity Technologies (UK) Ltd. // This file is part of Parity. // Parity is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Parity is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Parity. If not, see <http://www.gnu.org/licenses/>. var Abi = require('@parity/abi').default; var nextSubscriptionId = 0; var Contract = /** @class */ (function () { function Contract(api, abi) { var _this = this; if (!api) { throw new Error('API instance needs to be provided to Contract'); } if (!abi) { throw new Error('ABI needs to be provided to Contract instance'); } this._api = api; this._abi = new Abi(abi); this.getCallData = this.getCallData.bind(this); this._pollCheckRequest = this._pollCheckRequest.bind(this); this._pollTransactionReceipt = this._pollTransactionReceipt.bind(this); this._bindFunction = this._bindFunction.bind(this); this._bindEvent = this._bindEvent.bind(this); this._subscribeToChanges = this._subscribeToChanges.bind(this); this._unsubscribeFromChanges = this._unsubscribeFromChanges.bind(this); this._subscribeToBlock = this._subscribeToBlock.bind(this); this._subscribeToPendings = this._subscribeToPendings.bind(this); this._sendSubscriptionChanges = this._sendSubscriptionChanges.bind(this); this._subscriptions = {}; this._constructors = this._abi.constructors.map(this._bindFunction); this._functions = this._abi.functions.map(this._bindFunction); this._events = this._abi.events.map(this._bindEvent); this._instance = {}; this._events.forEach(function (evt) { _this._instance[evt.name] = evt; _this._instance[evt.signature] = evt; }); this._functions.forEach(function (fn) { _this._instance[fn.name] = fn; _this._instance[fn.signature] = fn; }); this._subscribedToPendings = false; this._pendingsSubscriptionId = null; this._subscribedToBlock = false; this._blockSubscriptionId = null; if (api && api.patch && api.patch.contract) { api.patch.contract(this); } } Object.defineProperty(Contract.prototype, "address", { get: function () { return this._address; }, enumerable: true, configurable: true }); Object.defineProperty(Contract.prototype, "constructors", { get: function () { return this._constructors; }, enumerable: true, configurable: true }); Object.defineProperty(Contract.prototype, "events", { get: function () { return this._events; }, enumerable: true, configurable: true }); Object.defineProperty(Contract.prototype, "functions", { get: function () { return this._functions; }, enumerable: true, configurable: true }); Object.defineProperty(Contract.prototype, "receipt", { get: function () { return this._receipt; }, enumerable: true, configurable: true }); Object.defineProperty(Contract.prototype, "instance", { get: function () { this._instance.address = this._address; return this._instance; }, enumerable: true, configurable: true }); Object.defineProperty(Contract.prototype, "api", { get: function () { return this._api; }, enumerable: true, configurable: true }); Object.defineProperty(Contract.prototype, "abi", { get: function () { return this._abi; }, enumerable: true, configurable: true }); Contract.prototype.at = function (address) { this._address = address; return this; }; Contract.prototype.deployEstimateGas = function (options, values) { var _options = this._encodeOptions(this.constructors[0], options, values); return this._api.eth.estimateGas(_options).then(function (gasEst) { return [gasEst, gasEst.multipliedBy(1.2)]; }); }; Contract.prototype.deploy = function (options, values, statecb, skipGasEstimate) { var _this = this; if (statecb === void 0) { statecb = function () { }; } if (skipGasEstimate === void 0) { skipGasEstimate = false; } var gasEstPromise; if (skipGasEstimate) { gasEstPromise = Promise.resolve(null); } else { statecb(null, { state: 'estimateGas' }); gasEstPromise = this.deployEstimateGas(options, values).then(function (_a) { var gasEst = _a[0], gas = _a[1]; return gas; }); } return gasEstPromise.then(function (_gas) { if (_gas) { options.gas = _gas.toFixed(0); } var gas = _gas || options.gas; statecb(null, { state: 'postTransaction', gas: gas }); var encodedOptions = _this._encodeOptions(_this.constructors[0], options, values); return _this._api.parity .postTransaction(encodedOptions) .then(function (requestId) { if (requestId.length !== 66) { statecb(null, { state: 'checkRequest', requestId: requestId }); return _this._pollCheckRequest(requestId); } return requestId; }) .then(function (txhash) { statecb(null, { state: 'getTransactionReceipt', txhash: txhash }); return _this._pollTransactionReceipt(txhash, gas); }) .then(function (receipt) { if (receipt.gasUsed.eq(gas)) { throw new Error("Contract not deployed, gasUsed == " + gas.toFixed(0)); } statecb(null, { state: 'hasReceipt', receipt: receipt }); _this._receipt = receipt; _this._address = receipt.contractAddress; return _this._address; }) .then(function (address) { statecb(null, { state: 'getCode' }); return _this._api.eth.getCode(_this._address); }) .then(function (code) { if (code === '0x') { throw new Error('Contract not deployed, getCode returned 0x'); } statecb(null, { state: 'completed' }); return _this._address; }); }); }; Contract.prototype.parseEventLogs = function (logs) { var _this = this; return logs .map(function (log) { var signature = log.topics[0].substr(2); var event = _this.events.find(function (evt) { return evt.signature === signature; }); if (!event) { console.warn("Unable to find event matching signature " + signature); return null; } try { var decoded = event.decodeLog(log.topics, log.data); log.params = {}; log.event = event.name; decoded.params.forEach(function (param, index) { var _a = param.token, type = _a.type, value = _a.value; var key = param.name || index; log.params[key] = { type: type, value: value }; }); return log; } catch (error) { console.warn('Error decoding log', log); console.warn(error); return null; } }) .filter(function (log) { return log; }); }; Contract.prototype.parseTransactionEvents = function (receipt) { receipt.logs = this.parseEventLogs(receipt.logs); return receipt; }; Contract.prototype._pollCheckRequest = function (requestId) { return this._api.pollMethod('parity_checkRequest', requestId); }; Contract.prototype._pollTransactionReceipt = function (txhash, gas) { return this.api.pollMethod('eth_getTransactionReceipt', txhash, function (receipt) { if (!receipt || !receipt.blockNumber || receipt.blockNumber.eq(0)) { return false; } return true; }); }; Contract.prototype.getCallData = function (func, options, values) { var data = options.data; var tokens = func ? Abi.encodeTokens(func.inputParamTypes(), values) : null; var call = tokens ? func.encodeCall(tokens) : null; if (data && data.substr(0, 2) === '0x') { data = data.substr(2); } return "0x" + (data || '') + (call || ''); }; Contract.prototype._encodeOptions = function (func, options, values) { var data = this.getCallData(func, options, values); return Object.assign({}, options, { data: data }); }; Contract.prototype._addOptionsTo = function (options) { if (options === void 0) { options = {}; } return Object.assign({ to: this._address }, options); }; Contract.prototype._bindFunction = function (func) { var _this = this; func.contract = this; func.call = function (_options, values) { if (_options === void 0) { _options = {}; } if (values === void 0) { values = []; } var rawTokens = !!_options.rawTokens; var options = Object.assign({}, _options); delete options.rawTokens; var callParams; try { callParams = _this._encodeOptions(func, _this._addOptionsTo(options), values); } catch (error) { return Promise.reject(error); } return _this._api.eth .call(callParams) .then(function (encoded) { return func.decodeOutput(encoded); }) .then(function (tokens) { if (rawTokens) { return tokens; } return tokens.map(function (token) { return token.value; }); }) .then(function (returns) { return (returns.length === 1 ? returns[0] : returns); }) .catch(function (error) { console.warn(func.name + ".call", values, error); throw error; }); }; if (!func.constant) { func.postTransaction = function (options, values) { if (values === void 0) { values = []; } var _options; try { _options = _this._encodeOptions(func, _this._addOptionsTo(options), values); } catch (error) { return Promise.reject(error); } return _this._api.parity.postTransaction(_options).catch(function (error) { console.warn(func.name + ".postTransaction", values, error); throw error; }); }; func.estimateGas = function (options, values) { if (values === void 0) { values = []; } var _options = _this._encodeOptions(func, _this._addOptionsTo(options), values); return _this._api.eth.estimateGas(_options).catch(function (error) { console.warn(func.name + ".estimateGas", values, error); throw error; }); }; } return func; }; Contract.prototype._bindEvent = function (event) { var _this = this; event.subscribe = function (options, callback, autoRemove) { if (options === void 0) { options = {}; } return _this._subscribe(event, options, callback, autoRemove); }; event.unsubscribe = function (subscriptionId) { return _this.unsubscribe(subscriptionId); }; event.getAllLogs = function (options) { if (options === void 0) { options = {}; } return _this.getAllLogs(event); }; return event; }; Contract.prototype.getAllLogs = function (event, _options) { var _this = this; // Options as first parameter if (!_options && event && event.topics) { return this.getAllLogs(null, event); } var options = this._getFilterOptions(event, _options); options.fromBlock = 0; options.toBlock = 'latest'; return this._api.eth .getLogs(options) .then(function (logs) { return _this.parseEventLogs(logs); }); }; Contract.prototype._findEvent = function (eventName) { if (eventName === void 0) { eventName = null; } var event = eventName ? this._events.find(function (evt) { return evt.name === eventName; }) : null; if (eventName && !event) { var events = this._events.map(function (evt) { return evt.name; }).join(', '); throw new Error(eventName + " is not a valid eventName, subscribe using one of " + events + " (or null to include all)"); } return event; }; Contract.prototype._getFilterOptions = function (event, _options) { if (event === void 0) { event = null; } if (_options === void 0) { _options = {}; } var optionTopics = _options.topics || []; var signature = (event && event.signature) || null; // If event provided, remove the potential event signature // as the first element of the topics var topics = signature ? [signature].concat(optionTopics.filter(function (t, index) { return index > 0 || t !== signature; })) : optionTopics; var options = Object.assign({}, _options, { address: this._address, topics: topics }); return options; }; Contract.prototype._createEthFilter = function (event, _options) { if (event === void 0) { event = null; } var options = this._getFilterOptions(event, _options); return this._api.eth.newFilter(options); }; Contract.prototype.subscribe = function (eventName, options, callback, autoRemove) { if (eventName === void 0) { eventName = null; } if (options === void 0) { options = {}; } try { var event_1 = this._findEvent(eventName); return this._subscribe(event_1, options, callback, autoRemove); } catch (e) { return Promise.reject(e); } }; Contract.prototype._sendData = function (subscriptionId, error, logs) { var _a = this._subscriptions[subscriptionId], autoRemove = _a.autoRemove, callback = _a.callback; var result = true; try { result = callback(error, logs); } catch (error) { console.warn('_sendData', subscriptionId, error); } if (autoRemove && result && typeof result === 'boolean') { this.unsubscribe(subscriptionId); } }; Contract.prototype._subscribe = function (event, _options, callback, autoRemove) { var _this = this; if (event === void 0) { event = null; } if (autoRemove === void 0) { autoRemove = false; } var subscriptionId = nextSubscriptionId++; var skipInitFetch = _options.skipInitFetch; delete _options['skipInitFetch']; return this._createEthFilter(event, _options) .then(function (filterId) { _this._subscriptions[subscriptionId] = { options: _options, autoRemove: autoRemove, callback: callback, filterId: filterId, id: subscriptionId }; if (skipInitFetch) { _this._subscribeToChanges(); return subscriptionId; } return _this._api.eth.getFilterLogs(filterId).then(function (logs) { _this._sendData(subscriptionId, null, _this.parseEventLogs(logs)); _this._subscribeToChanges(); return subscriptionId; }); }) .catch(function (error) { console.warn('subscribe', event, _options, error); throw error; }); }; Contract.prototype.unsubscribe = function (subscriptionId) { var _this = this; return this._api.eth .uninstallFilter(this._subscriptions[subscriptionId].filterId) .catch(function (error) { console.error('unsubscribe', error); }) .then(function () { delete _this._subscriptions[subscriptionId]; _this._unsubscribeFromChanges(); }); }; Contract.prototype._subscribeToChanges = function () { var subscriptions = Object.values(this._subscriptions); var pendingSubscriptions = subscriptions.filter(function (s) { return s.options.toBlock && s.options.toBlock === 'pending'; }); var otherSubscriptions = subscriptions.filter(function (s) { return !(s.options.toBlock && s.options.toBlock === 'pending'); }); if (pendingSubscriptions.length > 0 && !this._subscribedToPendings) { this._subscribedToPendings = true; this._subscribeToPendings(); } if (otherSubscriptions.length > 0 && !this._subscribedToBlock) { this._subscribedToBlock = true; this._subscribeToBlock(); } }; Contract.prototype._unsubscribeFromChanges = function () { var subscriptions = Object.values(this._subscriptions); var pendingSubscriptions = subscriptions.filter(function (s) { return s.options.toBlock && s.options.toBlock === 'pending'; }); var otherSubscriptions = subscriptions.filter(function (s) { return !(s.options.toBlock && s.options.toBlock === 'pending'); }); if (pendingSubscriptions.length === 0 && this._subscribedToPendings) { this._subscribedToPendings = false; clearTimeout(this._pendingsSubscriptionId); } if (otherSubscriptions.length === 0 && this._subscribedToBlock) { this._subscribedToBlock = false; this._api.unsubscribe(this._blockSubscriptionId); } }; Contract.prototype._subscribeToBlock = function () { var _this = this; this._api .subscribe('eth_blockNumber', function (error) { if (error) { console.error('::_subscribeToBlock', error, error && error.stack); } var subscriptions = Object.values(_this._subscriptions).filter(function (s) { return !(s.options.toBlock && s.options.toBlock === 'pending'); }); _this._sendSubscriptionChanges(subscriptions); }) .then(function (blockSubId) { _this._blockSubscriptionId = blockSubId; }) .catch(function (e) { console.error('::_subscribeToBlock', e, e && e.stack); }); }; Contract.prototype._subscribeToPendings = function () { var _this = this; var subscriptions = Object.values(this._subscriptions).filter(function (s) { return s.options.toBlock && s.options.toBlock === 'pending'; }); var timeout = function () { return setTimeout(function () { return _this._subscribeToPendings(); }, 1000); }; this._sendSubscriptionChanges(subscriptions).then(function () { _this._pendingsSubscriptionId = timeout(); }); }; Contract.prototype._sendSubscriptionChanges = function (subscriptions) { var _this = this; return Promise.all(subscriptions.map(function (subscription) { return _this._api.eth.getFilterChanges(subscription.filterId); })) .then(function (logsArray) { logsArray.forEach(function (logs, index) { if (!logs || !logs.length) { return; } try { _this._sendData(subscriptions[index].id, null, _this.parseEventLogs(logs)); } catch (error) { console.error('_sendSubscriptionChanges', error); } }); }) .catch(function (error) { console.error('_sendSubscriptionChanges', error); }); }; return Contract; }()); module.exports = Contract;