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 abi_coder_1 = require("../utils/abi-coder");
|
||||
var address_1 = require("../utils/address");
|
||||
var bytes_1 = require("../utils/bytes");
|
||||
var bignumber_1 = require("../utils/bignumber");
|
||||
var bytes_1 = require("../utils/bytes");
|
||||
var properties_1 = require("../utils/properties");
|
||||
var web_1 = require("../utils/web");
|
||||
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 () {
|
||||
// https://github.com/Microsoft/TypeScript/issues/5453
|
||||
// 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 });
|
||||
}
|
||||
properties_1.defineReadOnly(this, 'estimate', {});
|
||||
properties_1.defineReadOnly(this, 'events', {});
|
||||
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
|
||||
if (!addressOrName) {
|
||||
properties_1.defineReadOnly(this, 'address', null);
|
||||
properties_1.defineReadOnly(this, 'addressPromise', Promise.resolve(null));
|
||||
return;
|
||||
}
|
||||
this._events = [];
|
||||
properties_1.defineReadOnly(this, 'address', addressOrName);
|
||||
properties_1.defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then(function (address) {
|
||||
if (address == null) {
|
||||
@ -209,68 +226,6 @@ var Contract = /** @class */ (function () {
|
||||
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", {
|
||||
get: function () { return this._onerror; },
|
||||
@ -371,6 +326,181 @@ var Contract = /** @class */ (function () {
|
||||
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;
|
||||
}());
|
||||
exports.Contract = Contract;
|
||||
|
@ -18,10 +18,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
// 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 bignumber_1 = require("../utils/bignumber");
|
||||
var bytes_1 = require("../utils/bytes");
|
||||
var hash_1 = require("../utils/hash");
|
||||
var keccak256_1 = require("../utils/keccak256");
|
||||
var properties_1 = require("../utils/properties");
|
||||
var errors = __importStar(require("../utils/errors"));
|
||||
var Description = /** @class */ (function () {
|
||||
@ -123,6 +125,46 @@ var EventDescription = /** @class */ (function (_super) {
|
||||
function EventDescription() {
|
||||
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) {
|
||||
// Strip the signature off of non-anonymous topics
|
||||
if (topics != null && !this.anonymous) {
|
||||
|
@ -2,15 +2,14 @@
|
||||
|
||||
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 { defaultAbiCoder } from '../utils/abi-coder';
|
||||
import { defaultAbiCoder, formatSignature, ParamType, parseSignature } from '../utils/abi-coder';
|
||||
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 { 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 * as errors from '../utils/errors';
|
||||
@ -48,7 +47,6 @@ function resolveAddresses(provider: Provider, value: any, paramType: ParamType |
|
||||
return Promise.resolve(value);
|
||||
}
|
||||
|
||||
|
||||
type RunFunction = (...params: Array<any>) => Promise<any>;
|
||||
|
||||
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 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> {
|
||||
[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 Contractish = Array<string | ParamType> | Interface | string;
|
||||
export class Contract {
|
||||
@ -192,7 +231,8 @@ export class Contract {
|
||||
|
||||
readonly estimate: Bucket<ContractEstimate>;
|
||||
readonly functions: Bucket<ContractFunction>;
|
||||
readonly events: Bucket<ContractEvent>;
|
||||
|
||||
readonly filters: Bucket<ContractFilter>;
|
||||
|
||||
readonly addressPromise: Promise<string>;
|
||||
|
||||
@ -227,9 +267,20 @@ export class Contract {
|
||||
}
|
||||
|
||||
defineReadOnly(this, 'estimate', { });
|
||||
defineReadOnly(this, 'events', { });
|
||||
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
|
||||
if (!addressOrName) {
|
||||
defineReadOnly(this, 'address', null);
|
||||
@ -237,6 +288,8 @@ export class Contract {
|
||||
return;
|
||||
}
|
||||
|
||||
this._events = [];
|
||||
|
||||
defineReadOnly(this, 'address', addressOrName);
|
||||
defineReadOnly(this, 'addressPromise', this.provider.resolveName(addressOrName).then((address) => {
|
||||
if (address == null) { throw new Error('name not found'); }
|
||||
@ -260,77 +313,6 @@ export class Contract {
|
||||
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; }
|
||||
@ -436,4 +418,189 @@ export class 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
|
||||
|
||||
import { getAddress } from '../utils/address';
|
||||
import { defaultAbiCoder, EventFragment, formatSignature, FunctionFragment, ParamType, parseSignature } from '../utils/abi-coder';
|
||||
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 { keccak256 } from '../utils/keccak256';
|
||||
import { defineReadOnly, defineFrozen } from '../utils/properties';
|
||||
|
||||
import * as errors from '../utils/errors';
|
||||
@ -109,6 +111,45 @@ export class EventDescription extends Description {
|
||||
readonly anonymous: boolean;
|
||||
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 {
|
||||
// Strip the signature off of non-anonymous topics
|
||||
if (topics != null && !this.anonymous) { topics = topics.slice(1); }
|
||||
|
@ -49,13 +49,13 @@ function TestContractEvents() {
|
||||
|
||||
function waitForEvent(eventName, expected) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
contract['on' + eventName.toLowerCase()] = function() {
|
||||
//console.dir(this, { depth: null });
|
||||
//console.log(this.event);
|
||||
this.removeListener();
|
||||
equals(this.event, Array.prototype.slice.call(arguments), expected);
|
||||
contract.on(eventName, function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var event = args.pop();
|
||||
event.removeListener();
|
||||
equals(event.event, args, expected);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user