diff --git a/js/src/contracts/verification.js b/js/src/contracts/verification.js
index 05b7ea35f..3940e0e18 100644
--- a/js/src/contracts/verification.js
+++ b/js/src/contracts/verification.js
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-import subscribeToEvent from '../util/subscribe-to-event';
+import subscribeToEvents from '../util/subscribe-to-events';
export const checkIfVerified = (contract, account) => {
return contract.instance.certified.call({}, [account]);
@@ -72,7 +72,7 @@ export const awaitPuzzle = (api, contract, account) => {
return blockNumber(api)
.then((block) => {
return new Promise((resolve, reject) => {
- const subscription = subscribeToEvent(contract, 'Puzzled', {
+ const subscription = subscribeToEvents(contract, ['Puzzled'], {
from: block.toNumber(),
filter: (log) => log.params.who.value === account
});
diff --git a/js/src/modals/ExecuteContract/executeContract.test.js b/js/src/modals/ExecuteContract/executeContract.test.js
index ce196408d..8d9e4ccca 100644
--- a/js/src/modals/ExecuteContract/executeContract.test.js
+++ b/js/src/modals/ExecuteContract/executeContract.test.js
@@ -59,6 +59,9 @@ const STORE = {
},
settings: {
backgroundSeed: ''
+ },
+ registry: {
+ reverse: {}
}
};
}
diff --git a/js/src/redux/middleware.js b/js/src/redux/middleware.js
index b62b4e5f2..bffeddc98 100644
--- a/js/src/redux/middleware.js
+++ b/js/src/redux/middleware.js
@@ -23,6 +23,7 @@ import SignerMiddleware from './providers/signerMiddleware';
import statusMiddleware from '~/views/Status/middleware';
import CertificationsMiddleware from './providers/certifications/middleware';
import ChainMiddleware from './providers/chainMiddleware';
+import RegistryMiddleware from './providers/registry/middleware';
export default function (api, browserHistory) {
const errors = new ErrorsMiddleware();
@@ -32,13 +33,15 @@ export default function (api, browserHistory) {
const certifications = new CertificationsMiddleware();
const routeMiddleware = routerMiddleware(browserHistory);
const chain = new ChainMiddleware();
+ const registry = new RegistryMiddleware(api);
const middleware = [
settings.toMiddleware(),
signer.toMiddleware(),
errors.toMiddleware(),
certifications.toMiddleware(),
- chain.toMiddleware()
+ chain.toMiddleware(),
+ registry
];
return middleware.concat(status, routeMiddleware, thunk);
diff --git a/js/src/redux/providers/registry/actions.js b/js/src/redux/providers/registry/actions.js
new file mode 100644
index 000000000..eeeb47f51
--- /dev/null
+++ b/js/src/redux/providers/registry/actions.js
@@ -0,0 +1,28 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+export const setReverse = (address, reverse) => ({
+ type: 'setReverse',
+ address, reverse
+});
+
+export const startCachingReverses = () => ({
+ type: 'startCachingReverses'
+});
+
+export const stopCachingReverses = () => ({
+ type: 'stopCachingReverses'
+});
diff --git a/js/src/redux/providers/registry/middleware.js b/js/src/redux/providers/registry/middleware.js
new file mode 100644
index 000000000..136a9eb08
--- /dev/null
+++ b/js/src/redux/providers/registry/middleware.js
@@ -0,0 +1,99 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import Contracts from '~/contracts';
+import subscribeToEvents from '~/util/subscribe-to-events';
+
+import registryABI from '~/contracts/abi/registry.json';
+
+import { setReverse, startCachingReverses } from './actions';
+
+export default (api) => (store) => {
+ let contract, subscription, timeout, interval;
+
+ let addressesToCheck = {};
+
+ const onLog = (log) => {
+ switch (log.event) {
+ case 'ReverseConfirmed':
+ addressesToCheck[log.params.reverse.value] = true;
+
+ break;
+ case 'ReverseRemoved':
+ delete addressesToCheck[log.params.reverse.value];
+
+ break;
+ }
+ };
+
+ const checkReverses = () => {
+ Object
+ .keys(addressesToCheck)
+ .forEach((address) => {
+ contract
+ .instance
+ .reverse
+ .call({}, [ address ])
+ .then((reverse) => store.dispatch(setReverse(address, reverse)));
+ });
+
+ addressesToCheck = {};
+ };
+
+ return (next) => (action) => {
+ switch (action.type) {
+ case 'initAll':
+ next(action);
+ store.dispatch(startCachingReverses());
+
+ break;
+ case 'startCachingReverses':
+ const { registry } = Contracts.get();
+
+ registry.getInstance()
+ .then((instance) => api.newContract(registryABI, instance.address))
+ .then((_contract) => {
+ contract = _contract;
+
+ subscription = subscribeToEvents(_contract, ['ReverseConfirmed', 'ReverseRemoved']);
+ subscription.on('log', onLog);
+
+ timeout = setTimeout(checkReverses, 5000);
+ interval = setInterval(checkReverses, 20000);
+ })
+ .catch((err) => {
+ console.error('Failed to start caching reverses:', err);
+ throw err;
+ });
+
+ break;
+ case 'stopCachingReverses':
+ if (subscription) {
+ subscription.unsubscribe();
+ }
+ if (interval) {
+ clearInterval(interval);
+ }
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+
+ break;
+ default:
+ next(action);
+ }
+ };
+};
diff --git a/js/src/redux/providers/registry/reducer.js b/js/src/redux/providers/registry/reducer.js
new file mode 100644
index 000000000..5c267d822
--- /dev/null
+++ b/js/src/redux/providers/registry/reducer.js
@@ -0,0 +1,33 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+const initialState = {
+ reverse: {} // cache for reverse lookup
+};
+
+export default (state = initialState, action) => {
+ if (action.type === 'setReverse') {
+ if (state.reverse[action.address] === action.reverse) {
+ return state;
+ }
+
+ return { ...state, reverse: {
+ ...state.reverse, [ action.address ]: action.reverse
+ } };
+ }
+
+ return state;
+};
diff --git a/js/src/redux/reducers.js b/js/src/redux/reducers.js
index 1156d1836..45408de92 100644
--- a/js/src/redux/reducers.js
+++ b/js/src/redux/reducers.js
@@ -24,6 +24,7 @@ import {
snackbarReducer, walletReducer
} from './providers';
import certificationsReducer from './providers/certifications/reducer';
+import registryReducer from './providers/registry/reducer';
import errorReducer from '~/ui/Errors/reducers';
import settingsReducer from '~/views/Settings/reducers';
@@ -43,6 +44,7 @@ export default function () {
images: imagesReducer,
nodeStatus: nodeStatusReducer,
personal: personalReducer,
+ registry: registryReducer,
signer: signerReducer,
snackbar: snackbarReducer,
wallet: walletReducer,
diff --git a/js/src/ui/Form/AddressSelect/addressSelect.js b/js/src/ui/Form/AddressSelect/addressSelect.js
index 692ff4285..7b09b5b07 100644
--- a/js/src/ui/Form/AddressSelect/addressSelect.js
+++ b/js/src/ui/Form/AddressSelect/addressSelect.js
@@ -56,6 +56,7 @@ class AddressSelect extends Component {
contacts: PropTypes.object,
contracts: PropTypes.object,
tokens: PropTypes.object,
+ reverse: PropTypes.object,
// Optional props
allowCopy: PropTypes.bool,
@@ -584,10 +585,12 @@ class AddressSelect extends Component {
function mapStateToProps (state) {
const { accountsInfo } = state.personal;
const { balances } = state.balances;
+ const { reverse } = state.registry;
return {
accountsInfo,
- balances
+ balances,
+ reverse
};
}
diff --git a/js/src/ui/Form/AddressSelect/addressSelectStore.js b/js/src/ui/Form/AddressSelect/addressSelectStore.js
index 26f9fe80e..bdb7c1fb2 100644
--- a/js/src/ui/Form/AddressSelect/addressSelectStore.js
+++ b/js/src/ui/Form/AddressSelect/addressSelectStore.js
@@ -16,7 +16,7 @@
import React from 'react';
import { observable, action } from 'mobx';
-import { flatMap } from 'lodash';
+import { flatMap, uniqBy } from 'lodash';
import { FormattedMessage } from 'react-intl';
import Contracts from '~/contracts';
@@ -30,7 +30,48 @@ export default class AddressSelectStore {
@observable registryValues = [];
initValues = [];
- regLookups = [];
+ regLookups = [
+ (query) => {
+ query = query.toLowerCase().trim();
+ if (query.length === 0 || query === '0x') {
+ return null;
+ }
+ const startsWithQuery = (s) => new RegExp('^' + query, 'i').test(s);
+
+ let address;
+ let name = this.reverse[query];
+
+ if (!name) {
+ const addr = Object
+ .keys(this.reverse)
+ .find((addr) => {
+ const name = this.reverse[addr];
+ return startsWithQuery(addr) || (name && startsWithQuery(name));
+ });
+
+ if (addr) {
+ address = addr;
+ name = this.reverse[addr];
+ } else {
+ return null;
+ }
+ }
+
+ return {
+ address,
+ name,
+ description: (
+
+ )
+ };
+ }
+ ];
constructor (api) {
this.api = api;
@@ -114,7 +155,8 @@ export default class AddressSelectStore {
}
@action setValues (props) {
- const { accounts = {}, contracts = {}, contacts = {} } = props;
+ const { accounts = {}, contracts = {}, contacts = {}, reverse = {} } = props;
+ this.reverse = reverse;
const accountsN = Object.keys(accounts).length;
const contractsN = Object.keys(contracts).length;
@@ -194,6 +236,8 @@ export default class AddressSelectStore {
.filter((result) => result && !ZERO.test(result.address));
})
.then((results) => {
+ results = uniqBy(results, (result) => result.address);
+
this.registryValues = results
.map((result) => {
const lowercaseAddress = result.address.toLowerCase();
diff --git a/js/src/util/subscribe-to-event.js b/js/src/util/subscribe-to-event.js
deleted file mode 100644
index 36d1f6d55..000000000
--- a/js/src/util/subscribe-to-event.js
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
-// This file is part of Parity.
-
-// Parity is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-
-// Parity is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-
-// You should have received a copy of the GNU General Public License
-// along with Parity. If not, see .
-
-import EventEmitter from 'eventemitter3';
-
-const defaults = {
- from: 0, // TODO
- to: 'latest',
- timeout: null,
- filter: () => true
-};
-
-const subscribeToEvent = (contract, name, opt = {}) => {
- opt = Object.assign({}, defaults, opt);
-
- let subscription = null;
- let timeout = null;
-
- const unsubscribe = () => {
- if (subscription) {
- contract.unsubscribe(subscription);
- subscription = null;
- }
- if (timeout) {
- clearTimeout(timeout);
- timeout = null;
- }
- };
-
- const emitter = new EventEmitter();
- emitter.unsubscribe = unsubscribe;
-
- if (typeof opt.timeout === 'number') {
- timeout = setTimeout(() => {
- unsubscribe();
- emitter.emit('timeout');
- }, opt.timeout);
- }
-
- const callback = (err, logs) => {
- if (err) {
- return emitter.emit('error', err);
- }
- for (let log of logs) {
- if (opt.filter(log)) {
- emitter.emit('log', log);
- }
- }
- };
-
- contract.subscribe(name, {
- fromBlock: opt.from, toBlock: opt.to
- }, callback)
- .then((_subscription) => {
- subscription = _subscription;
- })
- .catch((err) => {
- emitter.emit('error', err);
- });
-
- return emitter;
-};
-
-export default subscribeToEvent;
diff --git a/js/src/util/subscribe-to-events.js b/js/src/util/subscribe-to-events.js
new file mode 100644
index 000000000..48c990277
--- /dev/null
+++ b/js/src/util/subscribe-to-events.js
@@ -0,0 +1,97 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import EventEmitter from 'eventemitter3';
+
+const defaults = {
+ from: 0,
+ to: 'latest',
+ interval: 5000,
+ filter: () => true
+};
+
+const subscribeToEvents = (contract, events, opt = {}) => {
+ const { api } = contract;
+ opt = Object.assign({}, defaults, opt);
+
+ let filter = null;
+ let interval = null;
+
+ const unsubscribe = () => {
+ if (filter) {
+ filter
+ .then((filterId) => {
+ return api.eth.uninstallFilter(filterId);
+ })
+ .catch((err) => {
+ emitter.emit('error', err);
+ });
+ filter = null;
+ }
+ if (interval) {
+ clearInterval(interval);
+ interval = null;
+ }
+ };
+
+ const emitter = new EventEmitter();
+ emitter.unsubscribe = unsubscribe;
+
+ const fetcher = (method, filterId) => () => {
+ api
+ .eth[method](filterId)
+ .then((logs) => {
+ logs = contract.parseEventLogs(logs);
+
+ for (let log of logs) {
+ if (opt.filter(log)) {
+ emitter.emit('log', log);
+ emitter.emit(log.event, log);
+ }
+ }
+ })
+ .catch((err) => {
+ emitter.emit('error', err);
+ });
+ };
+
+ const signatures = events
+ .filter((event) => contract.instance[event])
+ .map((event) => contract.instance[event].signature);
+
+ filter = api.eth
+ .newFilter({
+ fromBlock: opt.from, toBlock: opt.to,
+ address: contract.address,
+ topics: [signatures]
+ })
+ .then((filterId) => {
+ fetcher('getFilterLogs', filterId)(); // fetch immediately
+
+ const fetchChanges = fetcher('getFilterChanges', filterId);
+ interval = setInterval(fetchChanges, opt.interval);
+
+ return filterId;
+ })
+ .catch((err) => {
+ emitter.emit('error', err);
+ throw err; // reject Promise
+ });
+
+ return emitter;
+};
+
+export default subscribeToEvents;