fix: lint (#68)

* refactor: support private methods and attributes
* Adding eslint and prettier config
* fix: lint files
* fix: promise then/catch
* fix: cb
This commit is contained in:
Richard Ramos 2020-02-12 15:53:33 -04:00 committed by GitHub
parent e3ce6d87e8
commit 2b70d0b260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1201 additions and 464 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
dist/
node_modules/

45
.eslintrc Normal file
View File

@ -0,0 +1,45 @@
{
"env": {
"node": true,
"browser": true,
"jest": true
},
"parser": "babel-eslint",
"plugins": ["standard", "babel"],
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings",
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"arrow-parens": ["error", "as-needed"],
"standard/object-curly-even-spacing": [2, "never"],
"standard/array-bracket-even-spacing": [2, "either"],
"standard/computed-property-even-spacing": [2, "even"],
"standard/no-callback-literal": [2, ["cb", "callback"]],
"babel/new-cap": 1,
"babel/camelcase": 1,
"babel/no-invalid-this": 1,
"babel/object-curly-spacing": 0,
"babel/quotes": 1,
"babel/semi": 1,
"babel/no-unused-expressions": 1,
"babel/valid-typeof": 1,
"comma-dangle": "off",
"object-curly-newline": "off",
"quotes": "off",
"semi": "off",
"object-curly-spacing": "off",
"import/no-extraneous-dependencies": "off",
"max-len": ["error", { "code": 120} ],
"no-undef": "off"
},
"overrides": []
}

12
.prettierrc Normal file
View File

@ -0,0 +1,12 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"useTabs": false,
"jsxSingleQuote": false,
"bracketSpacing": false,
"jsxBracketSameLine": false,
"arrowParens": "avoid",
"printWidth": 120
}

View File

@ -22,7 +22,8 @@ module.exports = api => {
corejs: 3
}
],
"@babel/plugin-proposal-class-properties"
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-private-methods"
]
},
browser: {
@ -46,7 +47,8 @@ module.exports = api => {
useESModules: true
}
],
"@babel/plugin-proposal-class-properties"
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-private-methods"
]
},
module: {
@ -70,7 +72,8 @@ module.exports = api => {
useESModules: true
}
],
"@babel/plugin-proposal-class-properties"
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-private-methods"
]
}
},

View File

@ -28,6 +28,7 @@
"browser": "./lib/index.js",
"module": "./module/index.js",
"scripts": {
"lint": "eslint 'src/**/*.js'",
"clean": "rimraf dist; rimraf lib; rimraf module; rimraf react;",
"build:browser": "cross-env BABEL_ENV=browser babel ./src --out-dir ./lib --source-maps --copy-files --ignore src/react/*",
"build:react": "cross-env BABEL_ENV=browser babel ./src/react --out-dir ./react --source-maps --copy-files",
@ -40,7 +41,13 @@
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/plugin-proposal-private-methods": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"babel-eslint": "^10.0.3",
"eslint": "^6.8.0",
"eslint-plugin-babel": "^5.3.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-standard": "^4.0.1",
"cross-env": "^7.0.0",
"ganache-core": "^2.10.1",
"npm-run-all": "^4.1.5",

View File

@ -1,37 +1,36 @@
import { fromEvent } from 'rxjs';
import loki from 'lokijs';
import {fromEvent} from "rxjs";
import Loki from "lokijs";
const getENV = function () {
if (typeof global !== 'undefined' && (global.android || global.NSObject)) {
// If no adapter assume nativescript which needs adapter to be passed manually
return 'NATIVESCRIPT'; //nativescript
const getENV = function() {
if (typeof global !== "undefined" && (global.android || global.NSObject)) {
// If no adapter assume nativescript which needs adapter to be passed manually
return "NATIVESCRIPT"; //nativescript
}
if (typeof window === 'undefined') {
return 'NODEJS';
}
if (typeof window === "undefined") {
return "NODEJS";
}
// TODO: LokiJS determines it's running in a browser if process is undefined,
// TODO: LokiJS determines it's running in a browser if process is undefined,
// yet we need webpack shim for process in a different package.
// this code ignores is the same getENV from loki except for the check for node webkit
if (typeof document !== 'undefined') {
if (document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1) {
return 'CORDOVA';
if (typeof document !== "undefined") {
if (document.URL.indexOf("http://") === -1 && document.URL.indexOf("https://") === -1) {
return "CORDOVA";
}
return 'BROWSER';
return "BROWSER";
}
return 'CORDOVA';
return "CORDOVA";
};
class Database {
constructor(dbFilename, events, cb) {
this.db = new loki(dbFilename, {
this.db = new Loki(dbFilename, {
autoload: true,
autoloadCallback: () => {
this.databaseInitialize()
this.databaseInitialize();
},
autosave: true,
env: getENV(),
@ -42,16 +41,16 @@ class Database {
}
databaseInitialize(cb) {
let dbChanges = fromEvent(this.events, "updateDB")
let dbChanges = fromEvent(this.events, "updateDB");
dbChanges.subscribe(() => {
this.db.saveDatabase()
})
this.db.saveDatabase();
});
}
getLastKnownEvent(eventKey) {
const collection = this.db.getCollection(eventKey);
if (collection && collection.count()){
return collection.max('blockNumber');
if (collection && collection.count()) {
return collection.max("blockNumber");
} else {
this.db.addCollection(eventKey);
}
@ -60,8 +59,8 @@ class Database {
getFirstKnownEvent(eventKey) {
const collection = this.db.getCollection(eventKey);
if (collection && collection.count()){
return collection.min('blockNumber');
if (collection && collection.count()) {
return collection.min("blockNumber");
} else {
this.db.addCollection(eventKey);
}
@ -75,12 +74,12 @@ class Database {
eventExists(eventKey, eventId) {
let collection = this.db.getCollection(eventKey);
if(!collection){
if (!collection) {
this.db.addCollection(eventKey);
return false;
}
return (collection.find({ 'id': eventId }).length > 0);
return collection.find({id: eventId}).length > 0;
}
recordEvent(eventKey, values) {
@ -90,18 +89,21 @@ class Database {
deleteEvent(eventKey, eventId) {
const collection = this.db.getCollection(eventKey);
if(collection)
collection.chain().find({ 'id': eventId }).remove();
if (collection)
collection.chain()
.find({id: eventId})
.remove();
}
deleteNewestBlocks(eventKey, gteBlockNum) {
if(gteBlockNum <= 0) return;
const collection = this.db.getCollection(eventKey);
if(collection)
collection.chain().find({ 'blockNumber': {'$gte': gteBlockNum}}).remove();
}
if (gteBlockNum <= 0) return;
const collection = this.db.getCollection(eventKey);
if (collection)
collection.chain()
.find({blockNumber: {$gte: gteBlockNum}})
.remove();
}
}
export default Database;

View File

@ -1,6 +1,5 @@
class NullDatabase {
constructor(_dbFilename, events, cb) {
this.events = events;
if (cb) {
@ -16,7 +15,7 @@ class NullDatabase {
getLastKnownEvent() {
return {
firstKnownBlock: 0,
firstKnownBlock: 0,
lastKnownBlock: 0
};
}
@ -29,15 +28,11 @@ class NullDatabase {
return false;
}
recordEvent(eventKey, values) {
}
recordEvent(eventKey, values) {}
deleteEvent(eventKey, eventId) {
}
deleteNewestBlocks(eventKey, gteBlockNum) {
}
deleteEvent(eventKey, eventId) {}
deleteNewestBlocks(eventKey, gteBlockNum) {}
}
export default NullDatabase;

View File

@ -1,10 +1,9 @@
import { fromEvent, ReplaySubject } from 'rxjs';
import hash from 'object-hash';
import HttpEventScanner from './httpEventScanner';
import WsEventScanner from './wsEventScanner';
import {fromEvent, ReplaySubject} from "rxjs";
import hash from "object-hash";
import HttpEventScanner from "./httpEventScanner";
import WsEventScanner from "./wsEventScanner";
class EventSyncer {
constructor(web3, events, db, isWebsocketProvider) {
this.events = events;
this.web3 = web3;
@ -14,7 +13,7 @@ class EventSyncer {
}
track(contractInstance, eventName, filters, gteBlockNum, networkId) {
const eventKey = hash(Object.assign({address: contractInstance.options.address, networkId}, (filters || {})));
const eventKey = hash(Object.assign({address: contractInstance.options.address, networkId}, filters || {}));
this.db.deleteNewestBlocks(eventKey, gteBlockNum);
@ -22,32 +21,36 @@ class EventSyncer {
let lastKnownBlock = this.db.getLastKnownEvent(eventKey);
let firstKnownBlock = this.db.getFirstKnownEvent(eventKey);
let sub = new ReplaySubject();
let contractObserver = fromEvent(this.events, eventKey)
let contractObserver = fromEvent(this.events, eventKey);
contractObserver.subscribe((e) => {
contractObserver.subscribe(e => {
if (!e) {
sub.complete();
return;
}
const id = hash({eventName, blockNumber: e.blockNumber, transactionIndex: e.transactionIndex, logIndex: e.logIndex});
const id = hash({
eventName,
blockNumber: e.blockNumber,
transactionIndex: e.transactionIndex,
logIndex: e.logIndex
});
// TODO: would be nice if this was smart enough to understand the type of returnValues and do the needed conversions
const eventData = {
id,
returnValues: {...e.returnValues},
blockNumber: e.blockNumber,
transactionIndex: e.transactionIndex,
blockNumber: e.blockNumber,
transactionIndex: e.transactionIndex,
logIndex: e.logIndex,
removed: e.removed
}
};
// TODO: test reorgs
sub.next({blockNumber: e.blockNumber, ...e.returnValues});
sub.next({blockNumber: e.blockNumber, ...e.returnValues});
if (e.removed){
if (e.removed) {
this.db.deleteEvent(eventKey, id);
return;
}
@ -59,26 +62,33 @@ class EventSyncer {
this.events.emit("updateDB");
});
const fnDBEvents = this.serveDBEvents(eventKey);
const fnPastEvents = this.getPastEvents(eventKey, contractInstance, eventName, filters);
if(this.isWebsocketProvider){
if (this.isWebsocketProvider) {
const fnSubscribe = this.subscribeToEvent(eventKey, contractInstance, eventName);
const eth_subscribe = this.eventScanner.scan(fnDBEvents, fnPastEvents, fnSubscribe, firstKnownBlock, lastKnownBlock, filterConditions);
const eth_subscribe = this.eventScanner.scan(
fnDBEvents,
fnPastEvents,
fnSubscribe,
firstKnownBlock,
lastKnownBlock,
filterConditions
);
const og_subscribe = sub.subscribe;
sub.subscribe = async (next, error, complete) => {
const s = og_subscribe.apply(sub, [next, error, complete]);
s.add(() => { // Removing web3js subscription when rxJS unsubscribe is executed
s.add(() => {
// Removing web3js subscription when rxJS unsubscribe is executed
eth_subscribe.then(susc => {
if(susc) {
if (susc) {
susc.unsubscribe();
}
});
});
return s;
}
};
} else {
this.eventScanner.scan(fnDBEvents, fnPastEvents, lastKnownBlock, filterConditions);
}
@ -86,31 +96,31 @@ class EventSyncer {
return sub;
}
getPastEvents = (eventKey, contractInstance, eventName, filters) => async (fromBlock, toBlock, hardLimit) => {
let events = await contractInstance.getPastEvents(eventName, { ...filters, fromBlock, toBlock });
getPastEvents = (eventKey, contractInstance, eventName, filters) => async (fromBlock, toBlock, hardLimit) => {
let events = await contractInstance.getPastEvents(eventName, {...filters, fromBlock, toBlock});
const cb = this.callbackFactory(filters, eventKey);
events.forEach(ev => cb(null, ev));
if(hardLimit && toBlock === hardLimit){ // Complete the observable
if (hardLimit && toBlock === hardLimit) {
// Complete the observable
this.events.emit(eventKey);
}
}
};
serveDBEvents = eventKey => (filters, toBlock, fromBlock = null) => {
const cb = this.callbackFactory(filters, eventKey);
const storedEvents = this.db.getEventsFor(eventKey).filter(x => x.blockNumber >= (fromBlock || filters.fromBlock) && x.blockNumber <= toBlock);
storedEvents.forEach(ev => {
cb(null, ev);
});
}
this.db.getEventsFor(eventKey)
.filter(x => x.blockNumber >= (fromBlock || filters.fromBlock) && x.blockNumber <= toBlock)
.forEach(ev => cb(null, ev));
};
subscribeToEvent = (eventKey, contractInstance, eventName) => (subscriptions, filters) => {
const cb = this.callbackFactory(filters, eventKey);
const s = contractInstance.events[eventName](filters, cb);
subscriptions.push(s);
return s;
}
};
callbackFactory = (filterConditions, eventKey) => (err, ev) => {
if (err) {
@ -131,9 +141,9 @@ class EventSyncer {
}
this.events.emit(eventKey, ev);
}
};
close(){
close() {
this.eventScanner.close();
}
}

View File

@ -1,15 +1,15 @@
import {sleep} from './utils'
import {sleep} from "./utils";
class HttpEventScanner {
constructor(web3){
constructor(web3) {
this.pollExecution = [];
this.web3 = web3;
}
async poll(execId, fn, timeout){
async poll(execId, fn, timeout) {
const shouldStop = await fn();
if(!this.pollExecution[execId] || shouldStop) return;
if(timeout) await sleep(timeout * 1000);
if (!this.pollExecution[execId] || shouldStop) return;
if (timeout) await sleep(timeout * 1000);
await this.poll(execId, fn, timeout);
}
@ -20,60 +20,68 @@ class HttpEventScanner {
// If there's a toBlock with a number
let toBlockFilter = 0;
if(filterConditions.toBlock && filterConditions.toBlock !== 'latest' ){
if (filterConditions.toBlock && filterConditions.toBlock !== "latest") {
toBlockFilter = filterConditions.toBlock;
}
const toBlockInPast = toBlockFilter && toBlockFilter < lastBlockNumberAtLoad;
const toBlockInPast = toBlockFilter && toBlockFilter < lastBlockNumberAtLoad;
// Determine if data already exists and return it.
let dbLimit = toBlockFilter > 0 ? Math.min(toBlockFilter, lastCachedBlock) : lastCachedBlock;
if(lastCachedBlock > 0 && filterConditions.fromBlock >= 0){
if (lastCachedBlock > 0 && filterConditions.fromBlock >= 0) {
serveDBEvents(filterConditions, dbLimit);
lastCachedBlock = lastCachedBlock + 1;
}
lastCachedBlock = Math.max(lastCachedBlock, filterConditions.fromBlock||0);
lastCachedBlock = Math.max(lastCachedBlock, filterConditions.fromBlock || 0);
// Get old events and store them in db
await this.poll(execId, async () => {
try {
const maxBlock = Math.min(lastCachedBlock + maxBlockRange, lastBlockNumberAtLoad);
const toBlock = toBlockInPast ? Math.min(maxBlock, toBlockFilter) : maxBlock;
const toBlockLimit = Math.min(await this.web3.getBlockNumber(), toBlock);
const toBlockLimit = Math.min(await this.web3.getBlockNumber(), toBlock);
if(toBlockLimit >= lastCachedBlock) {
await getPastEvents(lastCachedBlock, toBlockLimit, toBlockInPast ? toBlockFilter : null);
lastCachedBlock = toBlockLimit + 1;
if (toBlockLimit >= lastCachedBlock) {
await getPastEvents(lastCachedBlock, toBlockLimit, toBlockInPast ? toBlockFilter : null);
lastCachedBlock = toBlockLimit + 1;
}
} catch (e) {
console.log(e.toString());
}
} catch (e) {
console.log(e.toString());
}
// Should exit?
return (toBlockInPast && lastCachedBlock >= (toBlockFilter || 0)) || (lastCachedBlock > Math.max(lastBlockNumberAtLoad, toBlockInPast ? toBlockFilter || 0 : 0));
});
return (
(toBlockInPast && lastCachedBlock >= (toBlockFilter || 0)) ||
lastCachedBlock > Math.max(lastBlockNumberAtLoad, toBlockInPast ? toBlockFilter || 0 : 0)
);
});
if(toBlockInPast) return;
if (toBlockInPast) return;
// Get new data, with a timeout between requests
await this.poll(execId, async () => {
try {
let toBlockLimit = await this.web3.getBlockNumber()
if(toBlockLimit >= lastCachedBlock) {
await getPastEvents(lastCachedBlock, toBlockLimit, toBlockFilter || 0);
lastCachedBlock = toBlockLimit + 1;
await this.poll(
execId,
async () => {
try {
let toBlockLimit = await this.web3.getBlockNumber();
if (toBlockLimit >= lastCachedBlock) {
await getPastEvents(lastCachedBlock, toBlockLimit, toBlockFilter || 0);
lastCachedBlock = toBlockLimit + 1;
}
} catch (e) {
console.log(e.toString());
}
} catch (e) {
console.log(e.toString());
}
// Should exit?
return filterConditions.toBlock !== 'latest' && lastCachedBlock > Math.max(lastBlockNumberAtLoad, toBlockFilter || 0);
}, 1);
// Should exit?
return (
filterConditions.toBlock !== "latest" && lastCachedBlock > Math.max(lastBlockNumberAtLoad, toBlockFilter || 0)
);
},
1
);
}
close(){
close() {
this.pollExecution = Array(this.pollExecution.length).fill(false);
}
}

View File

@ -1,2 +1,2 @@
export {default} from './subspace';
export * from './operators';
export {default} from "./subspace";
export * from "./operators";

View File

@ -1,5 +1,5 @@
import { fromEvent, ReplaySubject } from 'rxjs';
import hash from 'object-hash';
import {fromEvent, ReplaySubject} from "rxjs";
import hash from "object-hash";
class LogSyncer {
constructor(web3, events, db) {
@ -10,33 +10,43 @@ class LogSyncer {
this.subscriptions = [];
}
track(options, inputsABI, gteBlockNum, networkId){
const eventKey = 'logs-' + hash(Object.assign({networkId}, options || {}));
track(options, inputsABI, gteBlockNum, networkId) {
const eventKey = "logs-" + hash(Object.assign({networkId}, options || {}));
const filterConditions = Object.assign({fromBlock: 0, toBlock: "latest"}, options || {});
this.db.deleteNewestBlocks(eventKey, gteBlockNum);
const eventSummary = this.db.getLastKnownEvent(eventKey);
const sub = new ReplaySubject();
const logObserver = fromEvent(this.events, eventKey)
const logObserver = fromEvent(this.events, eventKey);
logObserver.subscribe((e) => {
logObserver.subscribe(e => {
if (!e) return;
const id = hash({eventName: eventKey, blockNumber: e.blockNumber, transactionIndex: e.transactionIndex, logIndex: e.logIndex});
const id = hash({
eventName: eventKey,
blockNumber: e.blockNumber,
transactionIndex: e.transactionIndex,
logIndex: e.logIndex
});
// TODO: would be nice if this was smart enough to understand the type of returnValues and do the needed conversions
const eventData = {
id: hash({eventName: eventKey, blockNumber: e.blockNumber, transactionIndex: e.transactionIndex, logIndex: e.logIndex}),
id: hash({
eventName: eventKey,
blockNumber: e.blockNumber,
transactionIndex: e.transactionIndex,
logIndex: e.logIndex
}),
data: e.data,
address: e.address,
topics: e.topics,
removed: e.removed
}
};
const obsData = {blockNumber: e.blockNumber, data: e.data, address: e.address, topics: e.topics};
if(inputsABI){
if (inputsABI) {
eventData.returnValues = web3.eth.abi.decodeLog(inputsABI, e.data, e.topics.slice(1));
obsData.returnValues = eventData.returnValues;
}
@ -45,7 +55,7 @@ class LogSyncer {
sub.next(obsData);
if(e.removed){
if (e.removed) {
this.db.deleteEvent(eventKey, id);
return;
}
@ -57,20 +67,22 @@ class LogSyncer {
this.events.emit("updateDB");
});
const eth_subscribe = this._retrieveEvents(eventKey,
eventSummary.firstKnownBlock,
eventSummary.lastKnownBlock,
filterConditions
);
const eth_subscribe = this._retrieveEvents(
eventKey,
eventSummary.firstKnownBlock,
eventSummary.lastKnownBlock,
filterConditions
);
const og_subscribe = sub.subscribe;
sub.subscribe = (next, error, complete) => {
const s = og_subscribe.apply(sub, [next, error, complete]);
s.add(() => { // Removing web3js subscription when rxJS unsubscribe is executed
s.add(() => {
// Removing web3js subscription when rxJS unsubscribe is executed
if (eth_subscribe) eth_subscribe.unsubscribe();
});
return s;
}
};
return sub;
}
@ -79,46 +91,48 @@ class LogSyncer {
// TODO: this should be moved to a 'smart' module
// it should be able to do events X at the time to avoid slow downs as well as the 10k limit
if (firstKnownBlock == 0 || (firstKnownBlock > 0 && firstKnownBlock <= filterConditions.fromBlock)) {
if (filterConditions.toBlock === 'latest') {
if (filterConditions.toBlock === "latest") {
// emit DB Events [fromBlock, lastKnownBlock]
this._serveDBEvents(eventKey, filterConditions.fromBlock, lastKnownBlock, filterConditions);
// create a event subscription [lastKnownBlock + 1, ...]
let filters = Object.assign({}, filterConditions, { fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1 });
let filters = Object.assign({}, filterConditions, {
fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1
});
return this._subscribeToEvent(filters, eventKey);
}
else if (filterConditions.toBlock <= lastKnownBlock) {
} else if (filterConditions.toBlock <= lastKnownBlock) {
// emit DB Events [fromBlock, toBlock]
this._serveDBEvents(eventKey, filterConditions.fromBlock, filterConditions.toBlock, filterConditions);
}
else {
} else {
// emit DB Events [fromBlock, lastKnownBlock]
this._serveDBEvents(eventKey, filterConditions.fromBlock, lastKnownBlock, filterConditions);
// create a past event subscription [lastKnownBlock + 1, toBlock]
let filters = Object.assign({}, filterConditions, { fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1 });
let filters = Object.assign({}, filterConditions, {
fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1
});
this._getPastEvents(filters, eventKey);
}
}
else if (firstKnownBlock > 0) {
} else if (firstKnownBlock > 0) {
// create a past event subscription [ firstKnownBlock > fromBlock ? fromBlock : 0, firstKnownBlock - 1]
let fromBlock = firstKnownBlock > filterConditions.fromBlock ? filterConditions.fromBlock : 0;
let filters = Object.assign({}, filterConditions, { fromBlock, toBlock: firstKnownBlock - 1 });
let filters = Object.assign({}, filterConditions, {fromBlock, toBlock: firstKnownBlock - 1});
this._getPastEvents(filters, eventKey);
if (filterConditions.toBlock === 'latest') {
if (filterConditions.toBlock === "latest") {
// emit DB Events [firstKnownBlock, lastKnownBlock]
this._serveDBEvents(eventKey, firstKnownBlock, lastKnownBlock, filterConditions);
// create a subscription [lastKnownBlock + 1, ...]
const filters = Object.assign({}, filterConditions, { fromBlock: lastKnownBlock + 1 });
const filters = Object.assign({}, filterConditions, {fromBlock: lastKnownBlock + 1});
return this._subscribeToEvent(filters, eventKey);
}
else if (filterConditions.toBlock <= lastKnownBlock) {
} else if (filterConditions.toBlock <= lastKnownBlock) {
// emit DB Events [fromBlock, toBlock]
this._serveDBEvents(eventKey, filterConditions.fromBlock, filterConditions.toBlock, filterConditions);
}
else {
} else {
// emit DB Events [fromBlock, lastKnownBlock]
this._serveDBEvents(eventKey, filterConditions.fromBlock, lastKnownBlock, filterConditions);
// create a past event subscription [lastKnownBlock + 1, toBlock]
let filters = Object.assign({}, filterConditions, { fromBlock: lastKnownBlock + 1, toBlock: filterConditions.toBlock });
let filters = Object.assign({}, filterConditions, {
fromBlock: lastKnownBlock + 1,
toBlock: filterConditions.toBlock
});
this._getPastEvents(filters, eventKey);
}
}
@ -126,27 +140,24 @@ class LogSyncer {
_serveDBEvents(eventKey, firstKnownBlock, lastKnownBlock, filterConditions) {
const cb = this._parseEventCBFactory(filterConditions, eventKey);
const storedEvents = this.db.getEventsFor(eventKey).filter(x => x.blockNumber >= firstKnownBlock && x.blockNumber <= lastKnownBlock);
storedEvents.forEach(ev => {
cb(null, ev);
});
this.db.getEventsFor(eventKey)
.filter(x => x.blockNumber >= firstKnownBlock && x.blockNumber <= lastKnownBlock)
.forEach(ev => cb(null, ev));
}
_getPastEvents(filterConditions, eventKey) {
const cb = this._parseEventCBFactory(filterConditions, eventKey);
this.web3.getPastLogs(options, (err, logs) => {
if(err) {
if (err) {
throw new Error(err);
}
logs.forEach(l => {
cb(null, l);
})
logs.forEach(l => cb(null, l));
});
}
_subscribeToEvent(filterConditions, eventKey) {
const s = this.web3.subscribe('logs', filterConditions, this._parseEventCBFactory(filterConditions, eventKey));
const s = this.web3.subscribe("logs", filterConditions, this._parseEventCBFactory(filterConditions, eventKey));
this.subscriptions.push(s);
return s;
}
@ -158,24 +169,22 @@ class LogSyncer {
if (filterConditions) {
if (filterConditions.address && ev.address.toLowerCase() !== filterConditions.address.toLowerCase()) return;
if (filterConditions.topics){
if (filterConditions.topics) {
let shouldSkip = false;
filterConditions.topics.forEach((topic, i) => {
if (topic != null && (!ev.topics[i] || ev.topics[i].toLowerCase() !== topic.toLowerCase())){
if (topic != null && (!ev.topics[i] || ev.topics[i].toLowerCase() !== topic.toLowerCase())) {
shouldSkip = true;
}
});
if(shouldSkip) return;
if (shouldSkip) return;
}
}
this.events.emit(eventKey, ev);
}
};
close(){
this.subscriptions.forEach(x => {
x.unsubscribe();
})
close() {
this.subscriptions.forEach(x => x.unsubscribe());
}
}

View File

@ -1,5 +1,5 @@
import { of, pipe } from 'rxjs';
import { map, pluck, scan } from 'rxjs/operators';
import {pipe} from "rxjs";
import {map, scan} from "rxjs/operators";
export function $takeProps() {
const args = Object.values(arguments);
@ -10,7 +10,7 @@ export function $takeProps() {
r[a] = v[a];
});
return r;
}),
})
);
}
@ -22,33 +22,36 @@ of({a: 1, b:2, e: 1}, {a: 0, c: 1, b:3}, {a: 0, d: 1, b:1})
*/
export function $average(cb) {
return pipe(
scan((accum, curr) => {
return pipe(
scan(
(accum, curr) => {
let currentValue;
if (typeof cb === 'string' || cb instanceof String){
if (typeof cb === "string" || cb instanceof String) {
currentValue = curr[cb];
} else if(typeof cb === "function") {
} else if (typeof cb === "function") {
currentValue = cb(curr);
} else {
currentValue = curr;
}
return {
sum: accum.sum + currentValue,
count: accum.count + 1
}
}, { sum: 0, count: 0 }),
map(o => o.sum / o.count)
);
}
};
},
{sum: 0, count: 0}
),
map(o => o.sum / o.count)
);
}
export function $max(cb) {
return pipe(
scan((acc, curr) => {
let currentValue;
if (typeof cb === 'string' || cb instanceof String){
if (typeof cb === "string" || cb instanceof String) {
currentValue = curr[cb];
} else if(typeof cb === "function") {
} else if (typeof cb === "function") {
currentValue = cb(curr);
} else {
currentValue = curr;
@ -64,9 +67,9 @@ export function $min(cb) {
return pipe(
scan((acc, curr) => {
let currentValue;
if (typeof cb === 'string' || cb instanceof String){
if (typeof cb === "string" || cb instanceof String) {
currentValue = curr[cb];
} else if(typeof cb === "function") {
} else if (typeof cb === "function") {
currentValue = cb(curr);
} else {
currentValue = curr;
@ -83,9 +86,9 @@ export function $latest(num) {
scan((acc, curr) => {
let currentValue = curr;
acc.push(currentValue)
acc.push(currentValue);
if (acc.length > num) {
acc.shift()
acc.shift();
}
return acc;

View File

@ -1,5 +1,5 @@
import React, { Component } from "react";
import { isObservable } from "rxjs";
import React, {Component} from "react";
import {isObservable} from "rxjs";
export function observe(WrappedComponent) {
return class extends Component {
@ -9,11 +9,11 @@ export function observe(WrappedComponent) {
};
unsubscribe = prop => {
const subscriptions = { ...this.state.subscriptions };
const subscriptions = {...this.state.subscriptions};
if (subscriptions[prop]) subscriptions[prop].unsubscribe();
delete subscriptions[prop];
this.setState({ subscriptions });
this.setState({subscriptions});
};
subscribeToProp = prop => {

View File

@ -1,18 +1,27 @@
import { ReplaySubject, BehaviorSubject } from 'rxjs';
import { distinctUntilChanged, map } from 'rxjs/operators';
import equal from 'fast-deep-equal';
import Database from './database/database.js';
import NullDatabase from './database/nullDatabase.js';
import Events from 'events';
import Web3Eth from 'web3-eth';
import {isAddress} from './utils';
import stripHexPrefix from 'strip-hex-prefix';
import {hexToDec} from 'hex2dec';
import EventSyncer from './eventSyncer';
import LogSyncer from './logSyncer';
import hash from 'object-hash';
import {ReplaySubject, BehaviorSubject} from "rxjs";
import {distinctUntilChanged, map} from "rxjs/operators";
import equal from "fast-deep-equal";
import Database from "./database/database.js";
import NullDatabase from "./database/nullDatabase.js";
import Events from "events";
import Web3Eth from "web3-eth";
import {isAddress} from "./utils";
import stripHexPrefix from "strip-hex-prefix";
import {hexToDec} from "hex2dec";
import EventSyncer from "./eventSyncer";
import LogSyncer from "./logSyncer";
import hash from "object-hash";
export default class Subspace {
subjects = {};
callables = [];
newBlocksSubscription = null;
intervalTracker = null;
// Stats
latestBlockNumber = undefined;
latest10Blocks = [];
constructor(provider, options = {}) {
if (!provider.on) {
@ -26,25 +35,16 @@ export default class Subspace {
this.options = {};
this.options.refreshLastNBlocks = options.refreshLastNBlocks || 12;
this.options.callInterval = options.callInterval || 0;
this.options.dbFilename = options.dbFilename || 'subspace.db';
this.disableDatabase = options.disableDatabase;
this.options.dbFilename = options.dbFilename || "subspace.db";
this.options.disableDatabase = options.disableDatabase;
this.networkId = undefined;
this.isWebsocketProvider = options.disableSubscriptions ? false : !!provider.on;
// Stats
this.latestBlockNumber = undefined;
this.latest10Blocks = [];
this.subjects = {};
this.newBlocksSubscription = null;
this.intervalTracker = null;
this.callables = [];
}
init() {
return new Promise(async (resolve) => {
if (this.disableDatabase === true) {
return new Promise(async resolve => {
if (this.options.disableDatabase === true) {
this._db = new NullDatabase("", this.events);
} else {
this._db = new Database(this.options.dbFilename, this.events);
@ -56,12 +56,12 @@ export default class Subspace {
this.networkId = netId;
});
const block = await this.web3.getBlock('latest');
const block = await this.web3.getBlock("latest");
// Preload <= 10 blocks to calculate avg block time
if(block.number !== 0){
if (block.number !== 0) {
const minBlock = Math.max(0, block.number - 9);
for(let i = minBlock; i < block.number; i++){
for (let i = minBlock; i < block.number; i++) {
this.latest10Blocks.push(this.web3.getBlock(i));
}
@ -72,7 +72,7 @@ export default class Subspace {
this.latestBlockNumber = block.number;
this.latest10Blocks.push(block);
if(this.isWebsocketProvider){
if (this.isWebsocketProvider) {
this._initNewBlocksSubscription();
} else {
this.options.callInterval = this.options.callInterval || 1000;
@ -85,7 +85,7 @@ export default class Subspace {
contract(contractInstance) {
if (!contractInstance) {
throw new Error("please pass a contract instance to Subspace.contract()")
throw new Error("please pass a contract instance to Subspace.contract()");
}
let address = (contractInstance.options && contractInstance.options.address) || contractInstance.address || contractInstance.deployedAddress;
@ -106,40 +106,40 @@ export default class Subspace {
SubspaceContract.trackEvent = (eventName, filterConditionsOrCb) => {
return this.trackEvent(SubspaceContract, eventName, filterConditionsOrCb);
}
};
Object.keys(SubspaceContract.events).forEach(ev => {
if(!SubspaceContract.options.jsonInterface.find(x => x.type === 'event' && x.name == ev)) return;
SubspaceContract.events[ev].track = (filterConditionsOrCb) => this.trackEvent(SubspaceContract, ev, filterConditionsOrCb);
if (!SubspaceContract.options.jsonInterface.find(x => x.type === "event" && x.name == ev)) return;
SubspaceContract.events[ev].track = filterConditionsOrCb =>
this.trackEvent(SubspaceContract, ev, filterConditionsOrCb);
});
SubspaceContract.trackProperty = (propName, methodArgs, callArgs) => {
return this.trackProperty(SubspaceContract, propName, methodArgs, callArgs);
}
};
Object.keys(SubspaceContract.methods).forEach(methodName => {
const oldFunc = SubspaceContract.methods[methodName];
const _this = this;
const newFunc = function(){
const newFunc = function() {
const txObject = oldFunc.apply(null, arguments);
txObject.track = (callArgs) => _this.trackProperty(SubspaceContract, methodName, txObject.arguments, callArgs);
txObject.track = callArgs => _this.trackProperty(SubspaceContract, methodName, txObject.arguments, callArgs);
return txObject;
}
};
SubspaceContract.methods[methodName] = newFunc;
});
SubspaceContract.trackBalance = (erc20Address) => {
SubspaceContract.trackBalance = erc20Address => {
return this.trackBalance(SubspaceContract.options.address, erc20Address);
}
};
return SubspaceContract;
}
clearDB(collection) {
if (collection){
if (collection) {
// TODO: delete specific collection
} else {
// TODO: delete everything
@ -148,16 +148,14 @@ export default class Subspace {
_initNewBlocksSubscription() {
if (this.newBlocksSubscription != null || this.options.callInterval !== 0) return;
this.newBlocksSubscription = this.web3.subscribe('newBlockHeaders', (err, result) => {
this.newBlocksSubscription = this.web3.subscribe("newBlockHeaders", (err, result) => {
if (err) {
console.error(err);
return;
}
this.callables.forEach(fn => {
fn();
});
this.callables.forEach(fn => fn());
});
}
@ -165,21 +163,19 @@ export default class Subspace {
if (this.intervalTracker != null || this.options.callInterval === 0) return;
this.intervalTracker = setInterval(() => {
this.callables.forEach(fn => {
fn();
});
this.callables.forEach(fn => fn());
}, this.options.callInterval);
}
_getSubject(subjectHash, subjectCB){
if(this.subjects[subjectHash]) return this.subjects[subjectHash];
_getSubject(subjectHash, subjectCB) {
if (this.subjects[subjectHash]) return this.subjects[subjectHash];
this.subjects[subjectHash] = subjectCB();
return this.subjects[subjectHash];
}
#_addDistinctCallable(trackAttribute, cbBuilder, subject, subjectArg = undefined) {
_addDistinctCallable(trackAttribute, cbBuilder, SubjectType, subjectArg = undefined) {
return this._getSubject(trackAttribute, () => {
const sub = new subject(subjectArg);
const sub = new SubjectType(subjectArg);
const cb = cbBuilder(sub);
cb();
this.callables.push(cb);
@ -188,24 +184,31 @@ export default class Subspace {
}
trackEvent(contractInstance, eventName, filterConditions) {
const subjectHash = hash({address: contractInstance.options.address, networkId: this.networkId, eventName, filterConditions});
const subjectHash = hash({
address: contractInstance.options.address,
networkId: this.networkId,
eventName,
filterConditions
});
return this._getSubject(subjectHash, () => {
let deleteFrom = this.latestBlockNumber - this.options.refreshLastNBlocks;
let returnSub = this.eventSyncer.track(contractInstance, eventName, filterConditions, deleteFrom, this.networkId);
returnSub.map = (prop) => {
return returnSub.pipe(map((x) => {
if (typeof(prop) === "string") {
return x[prop];
}
if (Array.isArray(prop)) {
let newValues = {}
prop.forEach((p) => {
newValues[p] = x[p]
})
return newValues
}
}))
returnSub.map = prop => {
return returnSub.pipe(
map(x => {
if (typeof prop === "string") {
return x[prop];
}
if (Array.isArray(prop)) {
let newValues = {};
prop.forEach(p => {
newValues[p] = x[p];
});
return newValues;
}
})
);
};
return returnSub;
@ -213,128 +216,156 @@ export default class Subspace {
}
trackLogs(options, inputsABI) {
if(!this.isWebsocketProvider) console.warn("This method only works with websockets");
if (!this.isWebsocketProvider) console.warn("This method only works with websockets");
const subjectHash = hash({inputsABI, options});
return this._getSubject(subjectHash, () =>
const subjectHash = hash({inputsABI, options});
return this._getSubject(subjectHash, () =>
this.logSyncer.track(options, inputsABI, this.latestBlockNumber - this.options.refreshLastNBlocks, this.networkId)
);
}
trackProperty(contractInstance, propName, methodArgs = [], callArgs = {}) {
const subjectHash = hash({address: contractInstance.options.address, networkId: this.networkId, propName, methodArgs, callArgs});
const subjectHash = hash({
address: contractInstance.options.address,
networkId: this.networkId,
propName,
methodArgs,
callArgs
});
return this._getSubject(subjectHash, () => {
const subject = new ReplaySubject(1);
if (!Array.isArray(methodArgs)) {
methodArgs = [methodArgs]
methodArgs = [methodArgs];
}
const method = contractInstance.methods[propName].apply(contractInstance.methods[propName], methodArgs);
const callContractMethod = () => {
method.call.apply(method.call, [callArgs, (err, result) => {
if (err) {
subject.error(err);
return;
method.call.apply(method.call, [
callArgs,
(err, result) => {
if (err) {
subject.error(err);
return;
}
subject.next(result);
}
subject.next(result);
}]);
]);
};
callContractMethod();
this.callables.push(callContractMethod);
const returnSub = subject.pipe(distinctUntilChanged((a, b) => equal(a, b)));
returnSub.map = (prop) => {
return returnSub.pipe(map((x) => {
if (typeof(prop) === "string") {
return x[prop];
}
if (Array.isArray(prop)) {
let newValues = {}
prop.forEach((p) => {
newValues[p] = x[p]
})
return newValues
}
}))
}
returnSub.map = prop => {
return returnSub.pipe(
map(x => {
if (typeof prop === "string") {
return x[prop];
}
if (Array.isArray(prop)) {
let newValues = {};
prop.forEach(p => {
newValues[p] = x[p];
});
return newValues;
}
})
);
};
return returnSub;
});
}
trackBalance(address, erc20Address) {
if (!isAddress(address)) throw "invalid address"
if (erc20Address && !isAddress(erc20Address)) throw "invalid ERC20 contract address"
if (!isAddress(address)) throw "invalid address";
if (erc20Address && !isAddress(erc20Address)) throw "invalid ERC20 contract address";
const subjectHash = hash({address, erc20Address});
const getETHBalance = (cb) => {
const fn = this.web3.getBalance;
const subjectHash = hash({address, erc20Address});
const getETHBalance = cb => {
const fn = this.web3.getBalance;
fn.apply(fn, [address, cb]);
}
};
const getTokenBalance = (cb) => {
const fn = this.web3.call;
// balanceOf
const data = "0x70a08231" + "000000000000000000000000" + stripHexPrefix(address);
const getTokenBalance = cb => {
const fn = this.web3.call;
// balanceOf
const data = "0x70a08231" + "000000000000000000000000" + stripHexPrefix(address);
fn.apply(fn, [{to: erc20Address, data}, cb]);
}
};
let callFn;
if (!erc20Address){
callFn = subject => () => getETHBalance((err, balance) => {
if(err) {
if (!erc20Address) {
callFn = subject => () =>
getETHBalance((err, balance) => {
if (err) {
subject.error(err);
return;
}
subject.next(balance);
});
} else {
callFn = subject => () => getTokenBalance((err, balance) => {
if(err) {
subject.error(err);
return;
}
subject.next(balance);
});
callFn = subject => () =>
getTokenBalance((err, balance) => {
if (err) {
subject.error(err);
return;
}
subject.next(hexToDec(balance));
});
}
return this._addDistinctCallable(subjectHash, callFn, ReplaySubject, 1);
}
trackBlock() {
const blockCB = (subject) => () => {
this.web3.getBlock('latest').then(block => {
if(this.latest10Blocks[this.latest10Blocks.length - 1].number === block.number) return;
const blockCB = subject => () => {
this.web3
.getBlock("latest")
.then(block => {
if (this.latest10Blocks[this.latest10Blocks.length - 1].number === block.number) return;
this.latest10Blocks.push(block);
if(this.latest10Blocks.length > 10){
this.latest10Blocks.shift();
}
subject.next(block);
}).catch(error => subject.error(error));
this.latest10Blocks.push(block);
if (this.latest10Blocks.length > 10) {
this.latest10Blocks.shift();
}
subject.next(block);
})
.catch(error => subject.error(error));
};
return this._addDistinctCallable('blockObservable', blockCB, BehaviorSubject, this.latest10Blocks[this.latest10Blocks.length - 1]);
return this._addDistinctCallable(
"blockObservable",
blockCB,
BehaviorSubject,
this.latest10Blocks[this.latest10Blocks.length - 1]
);
}
trackBlockNumber() {
const blockNumberCB = (subject) => () => {
this.web3.getBlockNumber().then(blockNumber => subject.next(blockNumber)).catch(error => subject.error(error));
const blockNumberCB = subject => () => {
this.web3
.getBlockNumber()
.then(result => subject.next(result))
.catch(error => subject.error(error));
};
return this._addDistinctCallable('blockNumberObservable', blockNumberCB, ReplaySubject, 1);
return this._addDistinctCallable("blockNumberObservable", blockNumberCB, ReplaySubject, 1);
}
trackGasPrice() {
const gasPriceCB = (subject) => () => {
this.web3.getGasPrice().then(gasPrice => subject.next(gasPrice)).catch(error => subject.error(error));
const gasPriceCB = subject => () => {
this.web3
.getGasPrice()
.then(result => subject.next(result))
.catch(error => subject.error(error));
};
return this._addDistinctCallable('gasPriceObservable', gasPriceCB, ReplaySubject, 1);
return this._addDistinctCallable("gasPriceObservable", gasPriceCB, ReplaySubject, 1);
}
trackAverageBlocktime() {
@ -344,21 +375,21 @@ export default class Subspace {
const times = [];
for (let i = 1; i < this.latest10Blocks.length; i++) {
let time = this.latest10Blocks[i].timestamp - this.latest10Blocks[i - 1].timestamp;
times.push(time)
times.push(time);
}
return times.length ? Math.round(times.reduce((a, b) => a + b) / times.length) * 1000 : 0;
}
};
const avgTimeCB = (subject) => () => subject.next(calcAverage());
return this._addDistinctCallable('blockTimeObservable', avgTimeCB, BehaviorSubject, calcAverage());
const avgTimeCB = subject => () => subject.next(calcAverage());
return this._addDistinctCallable("blockTimeObservable", avgTimeCB, BehaviorSubject, calcAverage());
}
close(){
close() {
clearInterval(this.intervalTracker);
if (this.newBlocksSubscription) this.newBlocksSubscription.unsubscribe();
this.eventSyncer.close();
this.intervalTracker = null;
this.callables = [];
}
}
}

View File

@ -1,13 +1,7 @@
export function randomString() {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
export function isAddress(address) {
return /^(0x)?[0-9a-fA-F]{40}$/i.test(address)
};
return /^(0x)?[0-9a-fA-F]{40}$/i.test(address);
}
export function sleep(milliseconds) {
return new Promise(resolve => setTimeout(resolve, milliseconds));
};
}

View File

@ -9,58 +9,55 @@ class WsEventScanner {
// If there's a toBlock with a number
let toBlockFilter = 0;
if(filterConditions.toBlock && filterConditions.toBlock !== 'latest' ){
if (filterConditions.toBlock && filterConditions.toBlock !== "latest") {
toBlockFilter = filterConditions.toBlock;
}
const toBlockInPast = toBlockFilter && toBlockFilter < lastBlockNumberAtLoad;
const toBlockInPast = toBlockFilter && toBlockFilter < lastBlockNumberAtLoad;
const hardLimit = toBlockInPast ? toBlockFilter : null;
if (firstKnownBlock == 0 || (firstKnownBlock > 0 && firstKnownBlock <= filterConditions.fromBlock)) {
if (filterConditions.toBlock === 'latest') {
if (filterConditions.toBlock === "latest") {
// emit DB Events [fromBlock, lastKnownBlock]
serveDBEvents(filterConditions, lastKnownBlock);
// create a event subscription [lastKnownBlock + 1, ...]
let filters = Object.assign({}, filterConditions, { fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1 });
// create a event subscription [lastKnownBlock + 1, ...]
let filters = Object.assign({}, filterConditions, {
fromBlock: filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1
});
return subscribe(this.subscriptions, filters);
}
else if (filterConditions.toBlock <= lastKnownBlock) {
} else if (filterConditions.toBlock <= lastKnownBlock) {
// emit DB Events [fromBlock, toBlock]
serveDBEvents(filterConditions, filterConditions.toBlock);
}
else {
} else {
// emit DB Events [fromBlock, lastKnownBlock]
serveDBEvents(filterConditions, lastKnownBlock);
// get past events [lastKnownBlock + 1, toBlock]
const fromBlock = filterConditions.fromBlock > lastKnownBlock ? filterConditions.fromBlock : lastKnownBlock + 1;
await getPastEvents(fromBlock, filterConditions.toBlock, hardLimit);
await getPastEvents(fromBlock, filterConditions.toBlock, hardLimit);
}
}
else if (firstKnownBlock > 0) {
} else if (firstKnownBlock > 0) {
// get past events [ firstKnownBlock > fromBlock ? fromBlock : 0, firstKnownBlock - 1]
const fromBlock = firstKnownBlock > filterConditions.fromBlock ? filterConditions.fromBlock : 0;
await getPastEvents(fromBlock, firstKnownBlock - 1, hardLimit);
await getPastEvents(fromBlock, firstKnownBlock - 1, hardLimit);
if (filterConditions.toBlock === 'latest') {
if (filterConditions.toBlock === "latest") {
// emit DB Events [firstKnownBlock, lastKnownBlock]
serveDBEvents(filterConditions, lastKnownBlock, firstKnownBlock);
// create a subscription [lastKnownBlock + 1, ...]
const filters = Object.assign({}, filterConditions, { fromBlock: lastKnownBlock + 1 });
const filters = Object.assign({}, filterConditions, {fromBlock: lastKnownBlock + 1});
return subscribe(this.subscriptions, filters);
}
else if (filterConditions.toBlock <= lastKnownBlock) {
} else if (filterConditions.toBlock <= lastKnownBlock) {
// emit DB Events [fromBlock, toBlock]
serveDBEvents(filterConditions, filterConditions.toBlock);
}
else {
} else {
// emit DB Events [fromBlock, lastKnownBlock]
serveDBEvents(filterConditions, lastKnownBlock);
// get past events [lastKnownBlock + 1, toBlock]
await getPastEvents(lastKnownBlock + 1, filterConditions.toBlock, hardLimit);
await getPastEvents(lastKnownBlock + 1, filterConditions.toBlock, hardLimit);
}
}
}
close(){
close() {
this.subscriptions.forEach(x => x.unsubscribe());
}
}

807
yarn.lock

File diff suppressed because it is too large Load Diff