Added new-style events (removed old-style) to contracts and added filters to contracts and interfaces.
This commit is contained in:
parent
b7e143b4f3
commit
979e374270
@ -12,8 +12,8 @@ var provider_1 = require("../providers/provider");
|
|||||||
var wallet_1 = require("../wallet/wallet");
|
var wallet_1 = require("../wallet/wallet");
|
||||||
var abi_coder_1 = require("../utils/abi-coder");
|
var abi_coder_1 = require("../utils/abi-coder");
|
||||||
var address_1 = require("../utils/address");
|
var address_1 = require("../utils/address");
|
||||||
var bytes_1 = require("../utils/bytes");
|
|
||||||
var bignumber_1 = require("../utils/bignumber");
|
var bignumber_1 = require("../utils/bignumber");
|
||||||
|
var bytes_1 = require("../utils/bytes");
|
||||||
var properties_1 = require("../utils/properties");
|
var properties_1 = require("../utils/properties");
|
||||||
var web_1 = require("../utils/web");
|
var web_1 = require("../utils/web");
|
||||||
var errors = __importStar(require("../utils/errors"));
|
var errors = __importStar(require("../utils/errors"));
|
||||||
@ -151,6 +151,9 @@ function runMethod(contract, functionName, estimateOnly) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function getEventTag(filter) {
|
||||||
|
return (filter.address || '') + (filter.topics ? filter.topics.join(':') : '');
|
||||||
|
}
|
||||||
var Contract = /** @class */ (function () {
|
var Contract = /** @class */ (function () {
|
||||||
// https://github.com/Microsoft/TypeScript/issues/5453
|
// https://github.com/Microsoft/TypeScript/issues/5453
|
||||||
// Once this issue is resolved (there are open PR) we can do this nicer
|
// Once this issue is resolved (there are open PR) we can do this nicer
|
||||||
@ -178,14 +181,28 @@ var Contract = /** @class */ (function () {
|
|||||||
errors.throwError('invalid signer or provider', errors.INVALID_ARGUMENT, { arg: 'signerOrProvider', value: signerOrProvider });
|
errors.throwError('invalid signer or provider', errors.INVALID_ARGUMENT, { arg: 'signerOrProvider', value: signerOrProvider });
|
||||||
}
|
}
|
||||||
properties_1.defineReadOnly(this, 'estimate', {});
|
properties_1.defineReadOnly(this, 'estimate', {});
|
||||||
properties_1.defineReadOnly(this, 'events', {});
|
|
||||||
properties_1.defineReadOnly(this, 'functions', {});
|
properties_1.defineReadOnly(this, 'functions', {});
|
||||||
|
properties_1.defineReadOnly(this, 'filters', {});
|
||||||
|
Object.keys(this.interface.events).forEach(function (eventName) {
|
||||||
|
var event = _this.interface.events[eventName];
|
||||||
|
properties_1.defineReadOnly(_this.filters, eventName, function () {
|
||||||
|
var args = [];
|
||||||
|
for (var _i = 0; _i < arguments.length; _i++) {
|
||||||
|
args[_i] = arguments[_i];
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
address: _this.address,
|
||||||
|
topics: event.encodeTopics(args)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
// Not connected to an on-chain instance, so do not connect functions and events
|
// Not connected to an on-chain instance, so do not connect functions and events
|
||||||
if (!addressOrName) {
|
if (!addressOrName) {
|
||||||
properties_1.defineReadOnly(this, 'address', null);
|
properties_1.defineReadOnly(this, 'address', null);
|
||||||
properties_1.defineReadOnly(this, 'addressPromise', Promise.resolve(null));
|
properties_1.defineReadOnly(this, 'addressPromise', Promise.resolve(null));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this._events = [];
|
||||||
properties_1.defineReadOnly(this, 'address', addressOrName);
|
properties_1.defineReadOnly(this, 'address', addressOrName);
|
||||||
properties_1.defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then(function (address) {
|
properties_1.defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then(function (address) {
|
||||||
if (address == null) {
|
if (address == null) {
|
||||||
@ -209,68 +226,6 @@ var Contract = /** @class */ (function () {
|
|||||||
properties_1.defineReadOnly(_this.estimate, name, runMethod(_this, name, true));
|
properties_1.defineReadOnly(_this.estimate, name, runMethod(_this, name, true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Object.keys(this.interface.events).forEach(function (eventName) {
|
|
||||||
var eventInfo = _this.interface.events[eventName];
|
|
||||||
var eventCallback = null;
|
|
||||||
var contract = _this;
|
|
||||||
function handleEvent(log) {
|
|
||||||
contract.addressPromise.then(function (address) {
|
|
||||||
// Not meant for us (the topics just has the same name)
|
|
||||||
if (address != log.address) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
var result = eventInfo.decode(log.data, log.topics);
|
|
||||||
// Some useful things to have with the log
|
|
||||||
log.args = result;
|
|
||||||
log.event = eventName;
|
|
||||||
log.decode = eventInfo.decode;
|
|
||||||
log.removeListener = function () {
|
|
||||||
contract.provider.removeListener([eventInfo.topic], handleEvent);
|
|
||||||
};
|
|
||||||
log.getBlock = function () { return contract.provider.getBlock(log.blockHash); ; };
|
|
||||||
log.getTransaction = function () { return contract.provider.getTransaction(log.transactionHash); };
|
|
||||||
log.getTransactionReceipt = function () { return contract.provider.getTransactionReceipt(log.transactionHash); };
|
|
||||||
log.eventSignature = eventInfo.signature;
|
|
||||||
eventCallback.apply(log, Array.prototype.slice.call(result));
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
var onerror_1 = contract._onerror;
|
|
||||||
if (onerror_1) {
|
|
||||||
setTimeout(function () { onerror_1(error); });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}).catch(function (error) { });
|
|
||||||
}
|
|
||||||
var property = {
|
|
||||||
enumerable: true,
|
|
||||||
get: function () {
|
|
||||||
return eventCallback;
|
|
||||||
},
|
|
||||||
set: function (value) {
|
|
||||||
if (!value) {
|
|
||||||
value = null;
|
|
||||||
}
|
|
||||||
if (!contract.provider) {
|
|
||||||
errors.throwError('events require a provider or a signer with a provider', errors.UNSUPPORTED_OPERATION, { operation: 'events' });
|
|
||||||
}
|
|
||||||
if (!value && eventCallback) {
|
|
||||||
contract.provider.removeListener([eventInfo.topic], handleEvent);
|
|
||||||
}
|
|
||||||
else if (value && !eventCallback) {
|
|
||||||
contract.provider.on([eventInfo.topic], handleEvent);
|
|
||||||
}
|
|
||||||
eventCallback = value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var propertyName = 'on' + eventName.toLowerCase();
|
|
||||||
if (_this[propertyName] == null) {
|
|
||||||
Object.defineProperty(_this, propertyName, property);
|
|
||||||
}
|
|
||||||
Object.defineProperty(_this.events, eventName, property);
|
|
||||||
}, this);
|
|
||||||
}
|
}
|
||||||
Object.defineProperty(Contract.prototype, "onerror", {
|
Object.defineProperty(Contract.prototype, "onerror", {
|
||||||
get: function () { return this._onerror; },
|
get: function () { return this._onerror; },
|
||||||
@ -371,6 +326,181 @@ var Contract = /** @class */ (function () {
|
|||||||
return contract;
|
return contract;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Contract.prototype._getEventFilter = function (eventName) {
|
||||||
|
var _this = this;
|
||||||
|
if (typeof (eventName) === 'string') {
|
||||||
|
// Listen for any event
|
||||||
|
if (eventName === '*') {
|
||||||
|
return {
|
||||||
|
decode: function (log) {
|
||||||
|
return [_this.interface.parseLog(log)];
|
||||||
|
},
|
||||||
|
eventTag: '*',
|
||||||
|
filter: { address: this.address },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Normalize the eventName
|
||||||
|
if (eventName.indexOf('(') !== -1) {
|
||||||
|
eventName = abi_coder_1.formatSignature(abi_coder_1.parseSignature('event ' + eventName));
|
||||||
|
}
|
||||||
|
var event_1 = this.interface.events[eventName];
|
||||||
|
if (!event_1) {
|
||||||
|
errors.throwError('unknown event - ' + eventName, errors.INVALID_ARGUMENT, { argumnet: 'eventName', value: eventName });
|
||||||
|
}
|
||||||
|
var filter_1 = {
|
||||||
|
address: this.address,
|
||||||
|
topics: [event_1.topic]
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
decode: function (log) {
|
||||||
|
return event_1.decode(log.data, log.topics);
|
||||||
|
},
|
||||||
|
event: event_1,
|
||||||
|
eventTag: getEventTag(filter_1),
|
||||||
|
filter: filter_1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
var filter = {
|
||||||
|
address: this.address
|
||||||
|
};
|
||||||
|
// Find the matching event in the ABI; if none, we still allow filtering
|
||||||
|
// since it may be a filter for an otherwise unknown event
|
||||||
|
var event = null;
|
||||||
|
if (eventName.topics && eventName.topics[0]) {
|
||||||
|
filter.topics = eventName.topics;
|
||||||
|
for (var name in this.interface.events) {
|
||||||
|
if (name.indexOf('(') === -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var e = this.interface.events[name];
|
||||||
|
if (e.topic === eventName.topics[0].toLowerCase()) {
|
||||||
|
event = e;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
decode: function (log) {
|
||||||
|
if (event) {
|
||||||
|
return event.decode(log.data, log.topics);
|
||||||
|
}
|
||||||
|
return [log];
|
||||||
|
},
|
||||||
|
event: event,
|
||||||
|
eventTag: getEventTag(filter),
|
||||||
|
filter: filter
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Contract.prototype._addEventListener = function (eventFilter, listener, once) {
|
||||||
|
var _this = this;
|
||||||
|
if (!this.provider) {
|
||||||
|
errors.throwError('events require a provider or a signer with a provider', errors.UNSUPPORTED_OPERATION, { operation: 'once' });
|
||||||
|
}
|
||||||
|
var wrappedListener = function (log) {
|
||||||
|
var decoded = Array.prototype.slice.call(eventFilter.decode(log));
|
||||||
|
var event = properties_1.jsonCopy(log);
|
||||||
|
event.args = decoded;
|
||||||
|
event.decode = eventFilter.event.decode;
|
||||||
|
event.event = eventFilter.event.name;
|
||||||
|
event.eventSignature = eventFilter.event.signature;
|
||||||
|
event.removeListener = function () { _this.removeListener(eventFilter.filter, listener); };
|
||||||
|
event.getBlock = function () { return _this.provider.getBlock(log.blockHash); };
|
||||||
|
event.getTransaction = function () { return _this.provider.getTransactionReceipt(log.transactionHash); };
|
||||||
|
event.getTransactionReceipt = function () { return _this.provider.getTransactionReceipt(log.transactionHash); };
|
||||||
|
decoded.push(event);
|
||||||
|
_this.emit.apply(_this, [eventFilter.filter].concat(decoded));
|
||||||
|
};
|
||||||
|
this.provider.on(eventFilter.filter, wrappedListener);
|
||||||
|
this._events.push({ eventFilter: eventFilter, listener: listener, wrappedListener: wrappedListener, once: once });
|
||||||
|
};
|
||||||
|
Contract.prototype.on = function (event, listener) {
|
||||||
|
this._addEventListener(this._getEventFilter(event), listener, false);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Contract.prototype.once = function (event, listener) {
|
||||||
|
this._addEventListener(this._getEventFilter(event), listener, true);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Contract.prototype.addEventLisener = function (eventName, listener) {
|
||||||
|
return this.on(eventName, listener);
|
||||||
|
};
|
||||||
|
Contract.prototype.emit = function (eventName) {
|
||||||
|
var _this = this;
|
||||||
|
var args = [];
|
||||||
|
for (var _i = 1; _i < arguments.length; _i++) {
|
||||||
|
args[_i - 1] = arguments[_i];
|
||||||
|
}
|
||||||
|
if (!this.provider) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var result = false;
|
||||||
|
var eventFilter = this._getEventFilter(eventName);
|
||||||
|
this._events = this._events.filter(function (event) {
|
||||||
|
if (event.eventFilter.eventTag !== eventFilter.eventTag) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
setTimeout(function () {
|
||||||
|
event.listener.apply(_this, args);
|
||||||
|
}, 0);
|
||||||
|
result = true;
|
||||||
|
return !(event.once);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
Contract.prototype.listenerCount = function (eventName) {
|
||||||
|
if (!this.provider) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
var eventFilter = this._getEventFilter(eventName);
|
||||||
|
return this._events.filter(function (event) {
|
||||||
|
return event.eventFilter.eventTag === eventFilter.eventTag;
|
||||||
|
}).length;
|
||||||
|
};
|
||||||
|
Contract.prototype.listeners = function (eventName) {
|
||||||
|
if (!this.provider) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
var eventFilter = this._getEventFilter(eventName);
|
||||||
|
return this._events.filter(function (event) {
|
||||||
|
return event.eventFilter.eventTag === eventFilter.eventTag;
|
||||||
|
}).map(function (event) { return event.listener; });
|
||||||
|
};
|
||||||
|
Contract.prototype.removeAllListeners = function (eventName) {
|
||||||
|
if (!this.provider) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
var eventFilter = this._getEventFilter(eventName);
|
||||||
|
this._events = this._events.filter(function (event) {
|
||||||
|
return event.eventFilter.eventTag !== eventFilter.eventTag;
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Contract.prototype.removeListener = function (eventName, listener) {
|
||||||
|
var _this = this;
|
||||||
|
if (!this.provider) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
var found = false;
|
||||||
|
var eventFilter = this._getEventFilter(eventName);
|
||||||
|
this._events = this._events.filter(function (event) {
|
||||||
|
// Make sure this event and listener match
|
||||||
|
if (event.eventFilter.eventTag !== eventFilter.eventTag) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event.listener !== listener) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_this.provider.removeListener(event.eventFilter.filter, event.wrappedListener);
|
||||||
|
// Already found a matching event in a previous loop
|
||||||
|
if (found) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// REmove this event (returning false filters us out)
|
||||||
|
found = true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
return this;
|
||||||
|
};
|
||||||
return Contract;
|
return Contract;
|
||||||
}());
|
}());
|
||||||
exports.Contract = Contract;
|
exports.Contract = Contract;
|
||||||
|
@ -18,10 +18,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|||||||
};
|
};
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
||||||
|
var address_1 = require("../utils/address");
|
||||||
var abi_coder_1 = require("../utils/abi-coder");
|
var abi_coder_1 = require("../utils/abi-coder");
|
||||||
var bignumber_1 = require("../utils/bignumber");
|
var bignumber_1 = require("../utils/bignumber");
|
||||||
var bytes_1 = require("../utils/bytes");
|
var bytes_1 = require("../utils/bytes");
|
||||||
var hash_1 = require("../utils/hash");
|
var hash_1 = require("../utils/hash");
|
||||||
|
var keccak256_1 = require("../utils/keccak256");
|
||||||
var properties_1 = require("../utils/properties");
|
var properties_1 = require("../utils/properties");
|
||||||
var errors = __importStar(require("../utils/errors"));
|
var errors = __importStar(require("../utils/errors"));
|
||||||
var Description = /** @class */ (function () {
|
var Description = /** @class */ (function () {
|
||||||
@ -123,6 +125,46 @@ var EventDescription = /** @class */ (function (_super) {
|
|||||||
function EventDescription() {
|
function EventDescription() {
|
||||||
return _super !== null && _super.apply(this, arguments) || this;
|
return _super !== null && _super.apply(this, arguments) || this;
|
||||||
}
|
}
|
||||||
|
EventDescription.prototype.encodeTopics = function (params) {
|
||||||
|
var _this = this;
|
||||||
|
if (params.length > this.inputs.length) {
|
||||||
|
errors.throwError('too many arguments for ' + this.name, errors.UNEXPECTED_ARGUMENT, { maxCount: params.length, expectedCount: this.inputs.length });
|
||||||
|
}
|
||||||
|
var topics = [];
|
||||||
|
if (!this.anonymous) {
|
||||||
|
topics.push(this.topic);
|
||||||
|
}
|
||||||
|
params.forEach(function (arg, index) {
|
||||||
|
if (arg === null) {
|
||||||
|
topics.push(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var param = _this.inputs[index];
|
||||||
|
if (!param.indexed) {
|
||||||
|
errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
|
||||||
|
}
|
||||||
|
if (param.type === 'string') {
|
||||||
|
topics.push(hash_1.id(arg));
|
||||||
|
}
|
||||||
|
else if (param.type === 'bytes') {
|
||||||
|
topics.push(keccak256_1.keccak256(arg));
|
||||||
|
}
|
||||||
|
else if (param.type.indexOf('[') !== -1 || param.type.substring(0, 5) === 'tuple') {
|
||||||
|
errors.throwError('filtering with tuples or arrays not implemented yet; bug us on GitHub', errors.NOT_IMPLEMENTED, { operation: 'filter(array|tuple)' });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (param.type === 'address') {
|
||||||
|
address_1.getAddress(arg);
|
||||||
|
}
|
||||||
|
topics.push(bytes_1.hexZeroPad(bytes_1.hexlify(arg), 32).toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Trim off trailing nulls
|
||||||
|
while (topics.length && topics[topics.length - 1] === null) {
|
||||||
|
topics.pop();
|
||||||
|
}
|
||||||
|
return topics;
|
||||||
|
};
|
||||||
EventDescription.prototype.decode = function (data, topics) {
|
EventDescription.prototype.decode = function (data, topics) {
|
||||||
// Strip the signature off of non-anonymous topics
|
// Strip the signature off of non-anonymous topics
|
||||||
if (topics != null && !this.anonymous) {
|
if (topics != null && !this.anonymous) {
|
||||||
|
@ -2,15 +2,14 @@
|
|||||||
|
|
||||||
import { EventDescription, Interface } from './interface';
|
import { EventDescription, Interface } from './interface';
|
||||||
|
|
||||||
import { Provider, TransactionRequest, TransactionResponse } from '../providers/provider';
|
import { Block, Log, Provider, TransactionReceipt, TransactionRequest, TransactionResponse } from '../providers/provider';
|
||||||
import { Signer } from '../wallet/wallet';
|
import { Signer } from '../wallet/wallet';
|
||||||
|
|
||||||
import { defaultAbiCoder } from '../utils/abi-coder';
|
import { defaultAbiCoder, formatSignature, ParamType, parseSignature } from '../utils/abi-coder';
|
||||||
import { getContractAddress } from '../utils/address';
|
import { getContractAddress } from '../utils/address';
|
||||||
import { hexDataLength, hexDataSlice, isHexString } from '../utils/bytes';
|
|
||||||
import { ParamType } from '../utils/abi-coder';
|
|
||||||
import { BigNumber, ConstantZero } from '../utils/bignumber';
|
import { BigNumber, ConstantZero } from '../utils/bignumber';
|
||||||
import { defineReadOnly, shallowCopy } from '../utils/properties';
|
import { hexDataLength, hexDataSlice, isHexString } from '../utils/bytes';
|
||||||
|
import { defineReadOnly, jsonCopy, shallowCopy } from '../utils/properties';
|
||||||
import { poll } from '../utils/web';
|
import { poll } from '../utils/web';
|
||||||
|
|
||||||
import * as errors from '../utils/errors';
|
import * as errors from '../utils/errors';
|
||||||
@ -48,7 +47,6 @@ function resolveAddresses(provider: Provider, value: any, paramType: ParamType |
|
|||||||
return Promise.resolve(value);
|
return Promise.resolve(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type RunFunction = (...params: Array<any>) => Promise<any>;
|
type RunFunction = (...params: Array<any>) => Promise<any>;
|
||||||
|
|
||||||
function runMethod(contract: Contract, functionName: string, estimateOnly: boolean): RunFunction {
|
function runMethod(contract: Contract, functionName: string, estimateOnly: boolean): RunFunction {
|
||||||
@ -173,14 +171,55 @@ function runMethod(contract: Contract, functionName: string, estimateOnly: boole
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Listener = (...args: Array<any>) => void;
|
||||||
|
|
||||||
|
export type EventFilter = {
|
||||||
|
address?: string;
|
||||||
|
topics?: Array<string>;
|
||||||
|
// @TODO: Support OR-style topcis; backwards compatible to make this change
|
||||||
|
//topics?: Array<string | Array<string>>
|
||||||
|
};
|
||||||
|
|
||||||
export type ContractEstimate = (...params: Array<any>) => Promise<BigNumber>;
|
export type ContractEstimate = (...params: Array<any>) => Promise<BigNumber>;
|
||||||
export type ContractFunction = (...params: Array<any>) => Promise<any>;
|
export type ContractFunction = (...params: Array<any>) => Promise<any>;
|
||||||
export type ContractEvent = (...params: Array<any>) => void;
|
export type ContractFilter = (...params: Array<any>) => EventFilter;
|
||||||
|
|
||||||
|
export interface Event extends Log {
|
||||||
|
args: Array<any>;
|
||||||
|
decode: (data: string, topics?: Array<string>) => any;
|
||||||
|
event: string;
|
||||||
|
eventSignature: string;
|
||||||
|
|
||||||
|
removeListener: () => void;
|
||||||
|
|
||||||
|
getBlock: () => Promise<Block>;
|
||||||
|
getTransaction: () => Promise<TransactionResponse>;
|
||||||
|
getTransactionReceipt: () => Promise<TransactionReceipt>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEventTag(filter: EventFilter): string {
|
||||||
|
return (filter.address || '') + (filter.topics ? filter.topics.join(':'): '');
|
||||||
|
}
|
||||||
|
|
||||||
interface Bucket<T> {
|
interface Bucket<T> {
|
||||||
[name: string]: T;
|
[name: string]: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type _EventFilter = {
|
||||||
|
decode: (log: Log) => Array<any>;
|
||||||
|
event?: EventDescription;
|
||||||
|
eventTag: string;
|
||||||
|
filter: EventFilter;
|
||||||
|
};
|
||||||
|
|
||||||
|
type _Event = {
|
||||||
|
eventFilter: _EventFilter;
|
||||||
|
listener: Listener;
|
||||||
|
once: boolean;
|
||||||
|
wrappedListener: Listener;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type ErrorCallback = (error: Error) => void;
|
export type ErrorCallback = (error: Error) => void;
|
||||||
export type Contractish = Array<string | ParamType> | Interface | string;
|
export type Contractish = Array<string | ParamType> | Interface | string;
|
||||||
export class Contract {
|
export class Contract {
|
||||||
@ -192,7 +231,8 @@ export class Contract {
|
|||||||
|
|
||||||
readonly estimate: Bucket<ContractEstimate>;
|
readonly estimate: Bucket<ContractEstimate>;
|
||||||
readonly functions: Bucket<ContractFunction>;
|
readonly functions: Bucket<ContractFunction>;
|
||||||
readonly events: Bucket<ContractEvent>;
|
|
||||||
|
readonly filters: Bucket<ContractFilter>;
|
||||||
|
|
||||||
readonly addressPromise: Promise<string>;
|
readonly addressPromise: Promise<string>;
|
||||||
|
|
||||||
@ -227,9 +267,20 @@ export class Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defineReadOnly(this, 'estimate', { });
|
defineReadOnly(this, 'estimate', { });
|
||||||
defineReadOnly(this, 'events', { });
|
|
||||||
defineReadOnly(this, 'functions', { });
|
defineReadOnly(this, 'functions', { });
|
||||||
|
|
||||||
|
defineReadOnly(this, 'filters', { });
|
||||||
|
|
||||||
|
Object.keys(this.interface.events).forEach((eventName) => {
|
||||||
|
let event = this.interface.events[eventName];
|
||||||
|
defineReadOnly(this.filters, eventName, (...args: Array<any>) => {
|
||||||
|
return {
|
||||||
|
address: this.address,
|
||||||
|
topics: event.encodeTopics(args)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Not connected to an on-chain instance, so do not connect functions and events
|
// Not connected to an on-chain instance, so do not connect functions and events
|
||||||
if (!addressOrName) {
|
if (!addressOrName) {
|
||||||
defineReadOnly(this, 'address', null);
|
defineReadOnly(this, 'address', null);
|
||||||
@ -237,6 +288,8 @@ export class Contract {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._events = [];
|
||||||
|
|
||||||
defineReadOnly(this, 'address', addressOrName);
|
defineReadOnly(this, 'address', addressOrName);
|
||||||
defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then((address) => {
|
defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then((address) => {
|
||||||
if (address == null) { throw new Error('name not found'); }
|
if (address == null) { throw new Error('name not found'); }
|
||||||
@ -260,77 +313,6 @@ export class Contract {
|
|||||||
defineReadOnly(this.estimate, name, runMethod(this, name, true));
|
defineReadOnly(this.estimate, name, runMethod(this, name, true));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(this.interface.events).forEach((eventName) => {
|
|
||||||
let eventInfo: EventDescription = this.interface.events[eventName];
|
|
||||||
|
|
||||||
type Callback = (...args: Array<any>) => void;
|
|
||||||
let eventCallback: Callback = null;
|
|
||||||
|
|
||||||
let contract = this;
|
|
||||||
function handleEvent(log: any): void {
|
|
||||||
contract.addressPromise.then((address) => {
|
|
||||||
// Not meant for us (the topics just has the same name)
|
|
||||||
if (address != log.address) { return null; }
|
|
||||||
|
|
||||||
try {
|
|
||||||
let result = eventInfo.decode(log.data, log.topics);
|
|
||||||
|
|
||||||
// Some useful things to have with the log
|
|
||||||
log.args = result;
|
|
||||||
log.event = eventName;
|
|
||||||
log.decode = eventInfo.decode;
|
|
||||||
log.removeListener = function() {
|
|
||||||
contract.provider.removeListener([ eventInfo.topic ], handleEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.getBlock = function() { return contract.provider.getBlock(log.blockHash);; }
|
|
||||||
log.getTransaction = function() { return contract.provider.getTransaction(log.transactionHash); }
|
|
||||||
log.getTransactionReceipt = function() { return contract.provider.getTransactionReceipt(log.transactionHash); }
|
|
||||||
log.eventSignature = eventInfo.signature;
|
|
||||||
|
|
||||||
eventCallback.apply(log, Array.prototype.slice.call(result));
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
let onerror = contract._onerror;
|
|
||||||
if (onerror) { setTimeout(() => { onerror(error); }); }
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}).catch((error) => { });
|
|
||||||
}
|
|
||||||
|
|
||||||
var property = {
|
|
||||||
enumerable: true,
|
|
||||||
get: function() {
|
|
||||||
return eventCallback;
|
|
||||||
},
|
|
||||||
set: function(value: Callback) {
|
|
||||||
if (!value) { value = null; }
|
|
||||||
|
|
||||||
if (!contract.provider) {
|
|
||||||
errors.throwError('events require a provider or a signer with a provider', errors.UNSUPPORTED_OPERATION, { operation: 'events' })
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!value && eventCallback) {
|
|
||||||
contract.provider.removeListener([ eventInfo.topic ], handleEvent);
|
|
||||||
|
|
||||||
} else if (value && !eventCallback) {
|
|
||||||
contract.provider.on([ eventInfo.topic ], handleEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
eventCallback = value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var propertyName = 'on' + eventName.toLowerCase();
|
|
||||||
if ((<any>this)[propertyName] == null) {
|
|
||||||
Object.defineProperty(this, propertyName, property);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(this.events, eventName, property);
|
|
||||||
|
|
||||||
}, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get onerror() { return this._onerror; }
|
get onerror() { return this._onerror; }
|
||||||
@ -436,4 +418,189 @@ export class Contract {
|
|||||||
return contract;
|
return contract;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _events: Array<_Event>;
|
||||||
|
|
||||||
|
_getEventFilter(eventName: EventFilter | string): _EventFilter {
|
||||||
|
if (typeof(eventName) === 'string') {
|
||||||
|
|
||||||
|
// Listen for any event
|
||||||
|
if (eventName === '*') {
|
||||||
|
return {
|
||||||
|
decode: (log: Log) => {
|
||||||
|
return [ this.interface.parseLog(log) ];
|
||||||
|
},
|
||||||
|
eventTag: '*',
|
||||||
|
filter: { address: this.address },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the eventName
|
||||||
|
if (eventName.indexOf('(') !== -1) {
|
||||||
|
eventName = formatSignature(parseSignature('event ' + eventName));
|
||||||
|
}
|
||||||
|
|
||||||
|
let event = this.interface.events[eventName];
|
||||||
|
if (!event) {
|
||||||
|
errors.throwError('unknown event - ' + eventName, errors.INVALID_ARGUMENT, { argumnet: 'eventName', value: eventName });
|
||||||
|
}
|
||||||
|
|
||||||
|
let filter = {
|
||||||
|
address: this.address,
|
||||||
|
topics: [ event.topic ]
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
decode: (log: Log) => {
|
||||||
|
return event.decode(log.data, log.topics)
|
||||||
|
},
|
||||||
|
event: event,
|
||||||
|
eventTag: getEventTag(filter),
|
||||||
|
filter: filter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let filter: EventFilter = {
|
||||||
|
address: this.address
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the matching event in the ABI; if none, we still allow filtering
|
||||||
|
// since it may be a filter for an otherwise unknown event
|
||||||
|
let event: EventDescription = null;
|
||||||
|
if (eventName.topics && eventName.topics[0]) {
|
||||||
|
filter.topics = eventName.topics;
|
||||||
|
for (var name in this.interface.events) {
|
||||||
|
if (name.indexOf('(') === -1) { continue; }
|
||||||
|
let e = this.interface.events[name];
|
||||||
|
if (e.topic === eventName.topics[0].toLowerCase()) {
|
||||||
|
event = e;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
decode: (log: Log) => {
|
||||||
|
if (event) { return event.decode(log.data, log.topics) }
|
||||||
|
return [ log ]
|
||||||
|
},
|
||||||
|
event: event,
|
||||||
|
eventTag: getEventTag(filter),
|
||||||
|
filter: filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addEventListener(eventFilter: _EventFilter, listener: Listener, once: boolean): void {
|
||||||
|
if (!this.provider) {
|
||||||
|
errors.throwError('events require a provider or a signer with a provider', errors.UNSUPPORTED_OPERATION, { operation: 'once' })
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrappedListener = (log: Log) => {
|
||||||
|
let decoded = Array.prototype.slice.call(eventFilter.decode(log));
|
||||||
|
|
||||||
|
let event = jsonCopy(log);
|
||||||
|
event.args = decoded;
|
||||||
|
event.decode = eventFilter.event.decode;
|
||||||
|
event.event = eventFilter.event.name;
|
||||||
|
event.eventSignature = eventFilter.event.signature;
|
||||||
|
|
||||||
|
event.removeListener = () => { this.removeListener(eventFilter.filter, listener); };
|
||||||
|
|
||||||
|
event.getBlock = () => { return this.provider.getBlock(log.blockHash); }
|
||||||
|
event.getTransaction = () => { return this.provider.getTransactionReceipt(log.transactionHash); }
|
||||||
|
event.getTransactionReceipt = () => { return this.provider.getTransactionReceipt(log.transactionHash); }
|
||||||
|
|
||||||
|
decoded.push(event);
|
||||||
|
this.emit(eventFilter.filter, ...decoded);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.provider.on(eventFilter.filter, wrappedListener);
|
||||||
|
this._events.push({ eventFilter: eventFilter, listener: listener, wrappedListener: wrappedListener, once: once });
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event: EventFilter | string, listener: Listener): Contract {
|
||||||
|
this._addEventListener(this._getEventFilter(event), listener, false);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
once(event: EventFilter | string, listener: Listener): Contract {
|
||||||
|
this._addEventListener(this._getEventFilter(event), listener, true);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventLisener(eventName: EventFilter | string, listener: Listener): Contract {
|
||||||
|
return this.on(eventName, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(eventName: EventFilter | string, ...args: Array<any>): boolean {
|
||||||
|
if (!this.provider) { return false; }
|
||||||
|
|
||||||
|
let result = false;
|
||||||
|
|
||||||
|
let eventFilter = this._getEventFilter(eventName);
|
||||||
|
this._events = this._events.filter((event) => {
|
||||||
|
if (event.eventFilter.eventTag !== eventFilter.eventTag) { return true; }
|
||||||
|
setTimeout(() => {
|
||||||
|
event.listener.apply(this, args);
|
||||||
|
}, 0);
|
||||||
|
result = true;
|
||||||
|
return !(event.once);
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerCount(eventName?: EventFilter | string): number {
|
||||||
|
if (!this.provider) { return 0; }
|
||||||
|
|
||||||
|
let eventFilter = this._getEventFilter(eventName);
|
||||||
|
return this._events.filter((event) => {
|
||||||
|
return event.eventFilter.eventTag === eventFilter.eventTag
|
||||||
|
}).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
listeners(eventName: EventFilter | string): Array<Listener> {
|
||||||
|
if (!this.provider) { return []; }
|
||||||
|
|
||||||
|
let eventFilter = this._getEventFilter(eventName);
|
||||||
|
return this._events.filter((event) => {
|
||||||
|
return event.eventFilter.eventTag === eventFilter.eventTag
|
||||||
|
}).map((event) => { return event.listener; });
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAllListeners(eventName: EventFilter | string): Contract {
|
||||||
|
if (!this.provider) { return this; }
|
||||||
|
|
||||||
|
let eventFilter = this._getEventFilter(eventName);
|
||||||
|
this._events = this._events.filter((event) => {
|
||||||
|
return event.eventFilter.eventTag !== eventFilter.eventTag
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeListener(eventName: any, listener: Listener): Contract {
|
||||||
|
if (!this.provider) { return this; }
|
||||||
|
|
||||||
|
let found = false;
|
||||||
|
|
||||||
|
let eventFilter = this._getEventFilter(eventName);
|
||||||
|
this._events = this._events.filter((event) => {
|
||||||
|
|
||||||
|
// Make sure this event and listener match
|
||||||
|
if (event.eventFilter.eventTag !== eventFilter.eventTag) { return true; }
|
||||||
|
if (event.listener !== listener) { return true; }
|
||||||
|
this.provider.removeListener(event.eventFilter.filter, event.wrappedListener);
|
||||||
|
|
||||||
|
// Already found a matching event in a previous loop
|
||||||
|
if (found) { return true; }
|
||||||
|
|
||||||
|
// REmove this event (returning false filters us out)
|
||||||
|
found = true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
||||||
|
|
||||||
|
import { getAddress } from '../utils/address';
|
||||||
import { defaultAbiCoder, EventFragment, formatSignature, FunctionFragment, ParamType, parseSignature } from '../utils/abi-coder';
|
import { defaultAbiCoder, EventFragment, formatSignature, FunctionFragment, ParamType, parseSignature } from '../utils/abi-coder';
|
||||||
import { BigNumber, bigNumberify, BigNumberish } from '../utils/bignumber';
|
import { BigNumber, bigNumberify, BigNumberish } from '../utils/bignumber';
|
||||||
import { arrayify, concat, isHexString } from '../utils/bytes';
|
import { arrayify, concat, hexlify, hexZeroPad, isHexString } from '../utils/bytes';
|
||||||
import { id } from '../utils/hash';
|
import { id } from '../utils/hash';
|
||||||
|
import { keccak256 } from '../utils/keccak256';
|
||||||
import { defineReadOnly, defineFrozen } from '../utils/properties';
|
import { defineReadOnly, defineFrozen } from '../utils/properties';
|
||||||
|
|
||||||
import * as errors from '../utils/errors';
|
import * as errors from '../utils/errors';
|
||||||
@ -109,6 +111,45 @@ export class EventDescription extends Description {
|
|||||||
readonly anonymous: boolean;
|
readonly anonymous: boolean;
|
||||||
readonly topic: string;
|
readonly topic: string;
|
||||||
|
|
||||||
|
encodeTopics(params: Array<any>): Array<string> {
|
||||||
|
if (params.length > this.inputs.length) {
|
||||||
|
errors.throwError('too many arguments for ' + this.name, errors.UNEXPECTED_ARGUMENT, { maxCount: params.length, expectedCount: this.inputs.length })
|
||||||
|
}
|
||||||
|
|
||||||
|
let topics: Array<string> = [];
|
||||||
|
if (!this.anonymous) { topics.push(this.topic); }
|
||||||
|
params.forEach((arg, index) => {
|
||||||
|
if (arg === null) {
|
||||||
|
topics.push(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let param = this.inputs[index];
|
||||||
|
|
||||||
|
if (!param.indexed) {
|
||||||
|
errors.throwError('cannot filter non-indexed parameters; must be null', errors.INVALID_ARGUMENT, { argument: (param.name || index), value: arg });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (param.type === 'string') {
|
||||||
|
topics.push(id(arg));
|
||||||
|
} else if (param.type === 'bytes') {
|
||||||
|
topics.push(keccak256(arg));
|
||||||
|
} else if (param.type.indexOf('[') !== -1 || param.type.substring(0, 5) === 'tuple') {
|
||||||
|
errors.throwError('filtering with tuples or arrays not implemented yet; bug us on GitHub', errors.NOT_IMPLEMENTED, { operation: 'filter(array|tuple)' });
|
||||||
|
} else {
|
||||||
|
if (param.type === 'address') { getAddress(arg); }
|
||||||
|
topics.push(hexZeroPad(hexlify(arg), 32).toLowerCase());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trim off trailing nulls
|
||||||
|
while (topics.length && topics[topics.length - 1] === null) {
|
||||||
|
topics.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return topics;
|
||||||
|
}
|
||||||
|
|
||||||
decode(data: string, topics?: Array<string>): any {
|
decode(data: string, topics?: Array<string>): any {
|
||||||
// Strip the signature off of non-anonymous topics
|
// Strip the signature off of non-anonymous topics
|
||||||
if (topics != null && !this.anonymous) { topics = topics.slice(1); }
|
if (topics != null && !this.anonymous) { topics = topics.slice(1); }
|
||||||
|
@ -49,13 +49,13 @@ function TestContractEvents() {
|
|||||||
|
|
||||||
function waitForEvent(eventName, expected) {
|
function waitForEvent(eventName, expected) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
contract['on' + eventName.toLowerCase()] = function() {
|
contract.on(eventName, function() {
|
||||||
//console.dir(this, { depth: null });
|
var args = Array.prototype.slice.call(arguments);
|
||||||
//console.log(this.event);
|
var event = args.pop();
|
||||||
this.removeListener();
|
event.removeListener();
|
||||||
equals(this.event, Array.prototype.slice.call(arguments), expected);
|
equals(event.event, args, expected);
|
||||||
resolve();
|
resolve();
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user