diff --git a/js/package.json b/js/package.json
index 2ecad7269..8672578c7 100644
--- a/js/package.json
+++ b/js/package.json
@@ -138,6 +138,7 @@
"blockies": "0.0.2",
"brace": "0.9.0",
"bytes": "2.4.0",
+ "crypto-js": "3.1.9-1",
"debounce": "1.0.0",
"es6-error": "4.0.0",
"es6-promise": "4.0.5",
diff --git a/js/src/api/util/sha3.js b/js/src/api/util/sha3.js
index 93b01d8dd..5a2c7c273 100644
--- a/js/src/api/util/sha3.js
+++ b/js/src/api/util/sha3.js
@@ -14,8 +14,21 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
-import { keccak_256 } from 'js-sha3'; // eslint-disable-line camelcase
+import CryptoJS from 'crypto-js';
+import CryptoSha3 from 'crypto-js/sha3';
-export function sha3 (value) {
- return `0x${keccak_256(value)}`;
+export function sha3 (value, options) {
+ if (options && options.encoding === 'hex') {
+ if (value.length > 2 && value.substr(0, 2) === '0x') {
+ value = value.substr(2);
+ }
+
+ value = CryptoJS.enc.Hex.parse(value);
+ }
+
+ const hash = CryptoSha3(value, {
+ outputLength: 256
+ }).toString();
+
+ return `0x${hash}`;
}
diff --git a/js/src/dapps/registry.js b/js/src/dapps/registry.js
index 0b8a8be55..3723bd9fa 100644
--- a/js/src/dapps/registry.js
+++ b/js/src/dapps/registry.js
@@ -34,3 +34,16 @@ ReactDOM.render(
,
document.querySelector('#container')
);
+
+if (module.hot) {
+ module.hot.accept('./registry/Container', () => {
+ require('./registry/Container');
+
+ ReactDOM.render(
+
+
+ ,
+ document.querySelector('#container')
+ );
+ });
+}
diff --git a/js/src/dapps/registry/Application/application.js b/js/src/dapps/registry/Application/application.js
index abfacc497..a1d5c0ef2 100644
--- a/js/src/dapps/registry/Application/application.js
+++ b/js/src/dapps/registry/Application/application.js
@@ -44,8 +44,8 @@ export default class Application extends Component {
static propTypes = {
accounts: PropTypes.object.isRequired,
- contract: nullableProptype(PropTypes.object).isRequired,
- fee: nullableProptype(PropTypes.object).isRequired
+ contract: nullableProptype(PropTypes.object.isRequired),
+ fee: nullableProptype(PropTypes.object.isRequired)
};
render () {
diff --git a/js/src/dapps/registry/Lookup/actions.js b/js/src/dapps/registry/Lookup/actions.js
index eb1c7db66..1e8ed5898 100644
--- a/js/src/dapps/registry/Lookup/actions.js
+++ b/js/src/dapps/registry/Lookup/actions.js
@@ -15,11 +15,13 @@
// along with Parity. If not, see .
import { sha3 } from '../parity.js';
+import { getOwner } from '../util/registry';
export const clear = () => ({ type: 'lookup clear' });
export const lookupStart = (name, key) => ({ type: 'lookup start', name, key });
export const reverseLookupStart = (address) => ({ type: 'reverseLookup start', address });
+export const ownerLookupStart = (name) => ({ type: 'ownerLookup start', name });
export const success = (action, result) => ({ type: `${action} success`, result: result });
@@ -48,24 +50,50 @@ export const lookup = (name, key) => (dispatch, getState) => {
});
};
-export const reverseLookup = (address) => (dispatch, getState) => {
+export const reverseLookup = (lookupAddress) => (dispatch, getState) => {
const { contract } = getState();
+
if (!contract) {
return;
}
- const reverse = contract.functions
- .find((f) => f.name === 'reverse');
+ dispatch(reverseLookupStart(lookupAddress));
- dispatch(reverseLookupStart(address));
-
- reverse.call({}, [ address ])
- .then((address) => dispatch(success('reverseLookup', address)))
+ contract.instance
+ .reverse
+ .call({}, [ lookupAddress ])
+ .then((address) => {
+ dispatch(success('reverseLookup', address));
+ })
.catch((err) => {
- console.error(`could not lookup reverse for ${address}`);
+ console.error(`could not lookup reverse for ${lookupAddress}`);
if (err) {
console.error(err.stack);
}
dispatch(fail('reverseLookup'));
});
};
+
+export const ownerLookup = (name) => (dispatch, getState) => {
+ const { contract } = getState();
+
+ if (!contract) {
+ return;
+ }
+
+ dispatch(ownerLookupStart(name));
+
+ return getOwner(contract, name)
+ .then((owner) => {
+ dispatch(success('ownerLookup', owner));
+ })
+ .catch((err) => {
+ console.error(`could not lookup owner for ${name}`);
+
+ if (err) {
+ console.error(err.stack);
+ }
+
+ dispatch(fail('ownerLookup'));
+ });
+};
diff --git a/js/src/dapps/registry/Lookup/lookup.js b/js/src/dapps/registry/Lookup/lookup.js
index bf01df115..f572cbb7d 100644
--- a/js/src/dapps/registry/Lookup/lookup.js
+++ b/js/src/dapps/registry/Lookup/lookup.js
@@ -23,13 +23,14 @@ import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
import RaisedButton from 'material-ui/RaisedButton';
import SearchIcon from 'material-ui/svg-icons/action/search';
+import keycode from 'keycode';
import { nullableProptype } from '~/util/proptypes';
import Address from '../ui/address.js';
import renderImage from '../ui/image.js';
-import { clear, lookup, reverseLookup } from './actions';
+import { clear, lookup, ownerLookup, reverseLookup } from './actions';
import styles from './lookup.css';
class Lookup extends Component {
@@ -39,6 +40,7 @@ class Lookup extends Component {
clear: PropTypes.func.isRequired,
lookup: PropTypes.func.isRequired,
+ ownerLookup: PropTypes.func.isRequired,
reverseLookup: PropTypes.func.isRequired
}
@@ -50,33 +52,6 @@ class Lookup extends Component {
const { input, type } = this.state;
const { result } = this.props;
- let output = '';
- if (result) {
- if (type === 'A') {
- output = (
-
-
-
- );
- } else if (type === 'IMG') {
- output = renderImage(result);
- } else if (type === 'CONTENT') {
- output = (
-
-
{ result }
-
Keep in mind that this is most likely the hash of the content you are looking for.
-
- );
- } else {
- output = (
- { result }
- );
- }
- }
-
return (
@@ -85,6 +60,7 @@ class Lookup extends Component {
hintText={ type === 'reverse' ? 'address' : 'name' }
value={ input }
onChange={ this.onInputChange }
+ onKeyDown={ this.onKeyDown }
/>
+
- { output }
+
+ { this.renderOutput(type, result) }
+
);
}
+ renderOutput (type, result) {
+ if (result === null) {
+ return null;
+ }
+
+ if (type === 'A') {
+ return (
+
+
+
+ );
+ }
+
+ if (type === 'owner') {
+ if (!result) {
+ return (
+ Not reserved yet
+ );
+ }
+
+ return (
+
+
+
+ );
+ }
+
+ if (type === 'IMG') {
+ return renderImage(result);
+ }
+
+ if (type === 'CONTENT') {
+ return (
+
+
{ result }
+
Keep in mind that this is most likely the hash of the content you are looking for.
+
+ );
+ }
+
+ return (
+ { result || 'No data' }
+ );
+ }
+
onInputChange = (e) => {
this.setState({ input: e.target.value });
- };
+ }
+
+ onKeyDown = (event) => {
+ const codeName = keycode(event);
+
+ if (codeName !== 'enter') {
+ return;
+ }
+
+ this.onLookupClick();
+ }
onTypeChange = (e, i, type) => {
this.setState({ type });
this.props.clear();
- };
+ }
onLookupClick = () => {
const { input, type } = this.state;
if (type === 'reverse') {
- this.props.reverseLookup(input);
- } else {
- this.props.lookup(input, type);
+ return this.props.reverseLookup(input);
}
- };
+
+ if (type === 'owner') {
+ return this.props.ownerLookup(input);
+ }
+
+ return this.props.lookup(input, type);
+ }
}
const mapStateToProps = (state) => state.lookup;
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
- clear, lookup, reverseLookup
+ clear, lookup, ownerLookup, reverseLookup
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Lookup);
diff --git a/js/src/dapps/registry/Lookup/reducers.js b/js/src/dapps/registry/Lookup/reducers.js
index b675fc702..8c804c28e 100644
--- a/js/src/dapps/registry/Lookup/reducers.js
+++ b/js/src/dapps/registry/Lookup/reducers.js
@@ -24,7 +24,7 @@ const initialState = {
export default (state = initialState, action) => {
const { type } = action;
- if (type.slice(0, 7) !== 'lookup ' && type.slice(0, 14) !== 'reverseLookup ') {
+ if (!/^(lookup|reverseLookup|ownerLookup)/.test(type)) {
return state;
}
diff --git a/js/src/dapps/registry/Names/actions.js b/js/src/dapps/registry/Names/actions.js
index 67867ca8e..2396278cb 100644
--- a/js/src/dapps/registry/Names/actions.js
+++ b/js/src/dapps/registry/Names/actions.js
@@ -15,8 +15,13 @@
// along with Parity. If not, see .
import { sha3, api } from '../parity.js';
+import { getOwner, isOwned } from '../util/registry';
import postTx from '../util/post-tx';
+export const clearError = () => ({
+ type: 'clearError'
+});
+
const alreadyQueued = (queue, action, name) =>
!!queue.find((entry) => entry.action === action && entry.name === name);
@@ -24,13 +29,14 @@ export const reserveStart = (name) => ({ type: 'names reserve start', name });
export const reserveSuccess = (name) => ({ type: 'names reserve success', name });
-export const reserveFail = (name) => ({ type: 'names reserve fail', name });
+export const reserveFail = (name, error) => ({ type: 'names reserve fail', name, error });
export const reserve = (name) => (dispatch, getState) => {
const state = getState();
const account = state.accounts.selected;
const contract = state.contract;
const fee = state.fee;
+
if (!contract || !account) {
return;
}
@@ -40,27 +46,34 @@ export const reserve = (name) => (dispatch, getState) => {
if (alreadyQueued(state.names.queue, 'reserve', name)) {
return;
}
- const reserve = contract.functions.find((f) => f.name === 'reserve');
dispatch(reserveStart(name));
- const options = {
- from: account.address,
- value: fee
- };
- const values = [
- sha3(name)
- ];
+ return isOwned(contract, name)
+ .then((owned) => {
+ if (owned) {
+ throw new Error(`"${name}" has already been reserved`);
+ }
- postTx(api, reserve, options, values)
+ const { reserve } = contract.instance;
+
+ const options = {
+ from: account.address,
+ value: fee
+ };
+ const values = [
+ sha3(name)
+ ];
+
+ return postTx(api, reserve, options, values);
+ })
.then((txHash) => {
dispatch(reserveSuccess(name));
})
.catch((err) => {
- console.error(`could not reserve ${name}`);
-
- if (err) {
- console.error(err.stack);
+ if (err.type !== 'REQUEST_REJECTED') {
+ console.error(`error rerserving ${name}`, err);
+ return dispatch(reserveFail(name, err));
}
dispatch(reserveFail(name));
@@ -71,43 +84,52 @@ export const dropStart = (name) => ({ type: 'names drop start', name });
export const dropSuccess = (name) => ({ type: 'names drop success', name });
-export const dropFail = (name) => ({ type: 'names drop fail', name });
+export const dropFail = (name, error) => ({ type: 'names drop fail', name, error });
export const drop = (name) => (dispatch, getState) => {
const state = getState();
const account = state.accounts.selected;
const contract = state.contract;
+
if (!contract || !account) {
return;
}
name = name.toLowerCase();
+
if (alreadyQueued(state.names.queue, 'drop', name)) {
return;
}
- const drop = contract.functions.find((f) => f.name === 'drop');
-
dispatch(dropStart(name));
- const options = {
- from: account.address
- };
- const values = [
- sha3(name)
- ];
+ return getOwner(contract, name)
+ .then((owner) => {
+ if (owner.toLowerCase() !== account.address.toLowerCase()) {
+ throw new Error(`you are not the owner of "${name}"`);
+ }
- postTx(api, drop, options, values)
+ const { drop } = contract.instance;
+
+ const options = {
+ from: account.address
+ };
+
+ const values = [
+ sha3(name)
+ ];
+
+ return postTx(api, drop, options, values);
+ })
.then((txhash) => {
dispatch(dropSuccess(name));
})
.catch((err) => {
- console.error(`could not drop ${name}`);
-
- if (err) {
- console.error(err.stack);
+ if (err.type !== 'REQUEST_REJECTED') {
+ console.error(`error dropping ${name}`, err);
+ return dispatch(dropFail(name, err));
}
- dispatch(reserveFail(name));
+ dispatch(dropFail(name));
});
};
diff --git a/js/src/dapps/registry/Names/names.css b/js/src/dapps/registry/Names/names.css
index b56387909..46d6a5560 100644
--- a/js/src/dapps/registry/Names/names.css
+++ b/js/src/dapps/registry/Names/names.css
@@ -35,7 +35,12 @@
.link {
color: #00BCD4;
text-decoration: none;
+
+ &:hover {
+ text-decoration: underline;
+ }
}
-.link:hover {
- text-decoration: underline;
+
+.error {
+ color: red;
}
diff --git a/js/src/dapps/registry/Names/names.js b/js/src/dapps/registry/Names/names.js
index c3f0e79f6..c34e172b9 100644
--- a/js/src/dapps/registry/Names/names.js
+++ b/js/src/dapps/registry/Names/names.js
@@ -24,9 +24,10 @@ import MenuItem from 'material-ui/MenuItem';
import RaisedButton from 'material-ui/RaisedButton';
import CheckIcon from 'material-ui/svg-icons/navigation/check';
+import { nullableProptype } from '~/util/proptypes';
import { fromWei } from '../parity.js';
-import { reserve, drop } from './actions';
+import { clearError, reserve, drop } from './actions';
import styles from './names.css';
const useSignerText = (Use the Signer to authenticate the following changes.
);
@@ -78,35 +79,21 @@ const renderQueue = (queue) => {
class Names extends Component {
static propTypes = {
+ error: nullableProptype(PropTypes.object.isRequired),
fee: PropTypes.object.isRequired,
pending: PropTypes.bool.isRequired,
queue: PropTypes.array.isRequired,
+ clearError: PropTypes.func.isRequired,
reserve: PropTypes.func.isRequired,
drop: PropTypes.func.isRequired
- }
+ };
state = {
action: 'reserve',
name: ''
};
- componentWillReceiveProps (nextProps) {
- const nextQueue = nextProps.queue;
- const prevQueue = this.props.queue;
-
- if (nextQueue.length > prevQueue.length) {
- const newQueued = nextQueue[nextQueue.length - 1];
- const newName = newQueued.name;
-
- if (newName !== this.state.name) {
- return;
- }
-
- this.setState({ name: '' });
- }
- }
-
render () {
const { action, name } = this.state;
const { fee, pending, queue } = this.props;
@@ -122,6 +109,7 @@ class Names extends Component {
: (To drop a name, you have to be the owner.
)
)
}
+ { this.renderError() }
+ { error.message }
+
+ );
+ }
+
onNameChange = (e) => {
+ this.clearError();
this.setState({ name: e.target.value });
};
+
onActionChange = (e, i, action) => {
+ this.clearError();
this.setState({ action });
};
+
onSubmitClick = () => {
const { action, name } = this.state;
+
if (action === 'reserve') {
- this.props.reserve(name);
- } else if (action === 'drop') {
- this.props.drop(name);
+ return this.props.reserve(name);
+ }
+
+ if (action === 'drop') {
+ return this.props.drop(name);
+ }
+ };
+
+ clearError = () => {
+ if (this.props.error) {
+ this.props.clearError();
}
};
}
const mapStateToProps = (state) => ({ ...state.names, fee: state.fee });
-const mapDispatchToProps = (dispatch) => bindActionCreators({ reserve, drop }, dispatch);
+const mapDispatchToProps = (dispatch) => bindActionCreators({ clearError, reserve, drop }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Names);
diff --git a/js/src/dapps/registry/Names/reducers.js b/js/src/dapps/registry/Names/reducers.js
index 17230ad40..461718a58 100644
--- a/js/src/dapps/registry/Names/reducers.js
+++ b/js/src/dapps/registry/Names/reducers.js
@@ -17,32 +17,55 @@
import { isAction, isStage, addToQueue, removeFromQueue } from '../util/actions';
const initialState = {
+ error: null,
pending: false,
queue: []
};
export default (state = initialState, action) => {
+ switch (action.type) {
+ case 'clearError':
+ return {
+ ...state,
+ error: null
+ };
+ }
+
if (isAction('names', 'reserve', action)) {
if (isStage('start', action)) {
return {
- ...state, pending: true,
+ ...state,
+ error: null,
+ pending: true,
queue: addToQueue(state.queue, 'reserve', action.name)
};
- } else if (isStage('success', action) || isStage('fail', action)) {
+ }
+
+ if (isStage('success', action) || isStage('fail', action)) {
return {
- ...state, pending: false,
+ ...state,
+ error: action.error || null,
+ pending: false,
queue: removeFromQueue(state.queue, 'reserve', action.name)
};
}
- } else if (isAction('names', 'drop', action)) {
+ }
+
+ if (isAction('names', 'drop', action)) {
if (isStage('start', action)) {
return {
- ...state, pending: true,
+ ...state,
+ error: null,
+ pending: true,
queue: addToQueue(state.queue, 'drop', action.name)
};
- } else if (isStage('success', action) || isStage('fail', action)) {
+ }
+
+ if (isStage('success', action) || isStage('fail', action)) {
return {
- ...state, pending: false,
+ ...state,
+ error: action.error || null,
+ pending: false,
queue: removeFromQueue(state.queue, 'drop', action.name)
};
}
diff --git a/js/src/dapps/registry/Records/actions.js b/js/src/dapps/registry/Records/actions.js
index 9afcb172c..f85304d5f 100644
--- a/js/src/dapps/registry/Records/actions.js
+++ b/js/src/dapps/registry/Records/actions.js
@@ -16,45 +16,57 @@
import { sha3, api } from '../parity.js';
import postTx from '../util/post-tx';
+import { getOwner } from '../util/registry';
+
+export const clearError = () => ({
+ type: 'clearError'
+});
export const start = (name, key, value) => ({ type: 'records update start', name, key, value });
export const success = () => ({ type: 'records update success' });
-export const fail = () => ({ type: 'records update error' });
+export const fail = (error) => ({ type: 'records update fail', error });
export const update = (name, key, value) => (dispatch, getState) => {
const state = getState();
const account = state.accounts.selected;
const contract = state.contract;
+
if (!contract || !account) {
return;
}
name = name.toLowerCase();
-
- const fnName = key === 'A' ? 'setAddress' : 'set';
- const setAddress = contract.functions.find((f) => f.name === fnName);
-
dispatch(start(name, key, value));
- const options = {
- from: account.address
- };
- const values = [
- sha3(name),
- key,
- value
- ];
+ return getOwner(contract, name)
+ .then((owner) => {
+ if (owner.toLowerCase() !== account.address.toLowerCase()) {
+ throw new Error(`you are not the owner of "${name}"`);
+ }
- postTx(api, setAddress, options, values)
+ const fnName = key === 'A' ? 'setAddress' : 'set';
+ const method = contract.instance[fnName];
+
+ const options = {
+ from: account.address
+ };
+
+ const values = [
+ sha3(name),
+ key,
+ value
+ ];
+
+ return postTx(api, method, options, values);
+ })
.then((txHash) => {
dispatch(success());
}).catch((err) => {
- console.error(`could not update ${key} record of ${name}`);
-
- if (err) {
- console.error(err.stack);
+ if (err.type !== 'REQUEST_REJECTED') {
+ console.error(`error updating ${name}`, err);
+ return dispatch(fail(err));
}
dispatch(fail());
diff --git a/js/src/dapps/registry/Records/records.css b/js/src/dapps/registry/Records/records.css
index e16ea4a15..03af5801f 100644
--- a/js/src/dapps/registry/Records/records.css
+++ b/js/src/dapps/registry/Records/records.css
@@ -36,3 +36,7 @@
flex-grow: 0;
flex-shrink: 0;
}
+
+.error {
+ color: red;
+}
diff --git a/js/src/dapps/registry/Records/records.js b/js/src/dapps/registry/Records/records.js
index f1d92cac8..f9c9cea76 100644
--- a/js/src/dapps/registry/Records/records.js
+++ b/js/src/dapps/registry/Records/records.js
@@ -24,17 +24,20 @@ import MenuItem from 'material-ui/MenuItem';
import RaisedButton from 'material-ui/RaisedButton';
import SaveIcon from 'material-ui/svg-icons/content/save';
-import { update } from './actions';
+import { nullableProptype } from '~/util/proptypes';
+import { clearError, update } from './actions';
import styles from './records.css';
class Records extends Component {
static propTypes = {
+ error: nullableProptype(PropTypes.object.isRequired),
pending: PropTypes.bool.isRequired,
name: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
+ clearError: PropTypes.func.isRequired,
update: PropTypes.func.isRequired
}
@@ -53,6 +56,7 @@ class Records extends Component {
You can only modify entries of names that you previously registered.
+ { this.renderError() }
+ { error.message }
+
+ );
+ }
+
onNameChange = (e) => {
+ this.clearError();
this.setState({ name: e.target.value });
};
+
onTypeChange = (e, i, type) => {
this.setState({ type });
};
+
onValueChange = (e) => {
this.setState({ value: e.target.value });
};
+
onSaveClick = () => {
const { name, type, value } = this.state;
this.props.update(name, type, value);
};
+
+ clearError = () => {
+ if (this.props.error) {
+ this.props.clearError();
+ }
+ };
}
const mapStateToProps = (state) => state.records;
-const mapDispatchToProps = (dispatch) => bindActionCreators({ update }, dispatch);
+const mapDispatchToProps = (dispatch) => bindActionCreators({ clearError, update }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Records);
diff --git a/js/src/dapps/registry/Records/reducers.js b/js/src/dapps/registry/Records/reducers.js
index 9629e8149..2dd45c012 100644
--- a/js/src/dapps/registry/Records/reducers.js
+++ b/js/src/dapps/registry/Records/reducers.js
@@ -17,11 +17,20 @@
import { isAction, isStage } from '../util/actions';
const initialState = {
+ error: null,
pending: false,
name: '', type: '', value: ''
};
export default (state = initialState, action) => {
+ switch (action.type) {
+ case 'clearError':
+ return {
+ ...state,
+ error: null
+ };
+ }
+
if (!isAction('records', 'update', action)) {
return state;
}
@@ -29,11 +38,15 @@ export default (state = initialState, action) => {
if (isStage('start', action)) {
return {
...state, pending: true,
- name: action.name, type: action.entry, value: action.value
+ error: null,
+ name: action.name, type: action.key, value: action.value
};
- } else if (isStage('success', action) || isStage('fail', action)) {
+ }
+
+ if (isStage('success', action) || isStage('fail', action)) {
return {
...state, pending: false,
+ error: action.error || null,
name: initialState.name, type: initialState.type, value: initialState.value
};
}
diff --git a/js/src/dapps/registry/Reverse/actions.js b/js/src/dapps/registry/Reverse/actions.js
index 07a1afade..bccd60f2f 100644
--- a/js/src/dapps/registry/Reverse/actions.js
+++ b/js/src/dapps/registry/Reverse/actions.js
@@ -16,44 +16,58 @@
import { api } from '../parity.js';
import postTx from '../util/post-tx';
+import { getOwner } from '../util/registry';
+
+export const clearError = () => ({
+ type: 'clearError'
+});
export const start = (action, name, address) => ({ type: `reverse ${action} start`, name, address });
export const success = (action) => ({ type: `reverse ${action} success` });
-export const fail = (action) => ({ type: `reverse ${action} error` });
+export const fail = (action, error) => ({ type: `reverse ${action} fail`, error });
export const propose = (name, address) => (dispatch, getState) => {
const state = getState();
const account = state.accounts.selected;
const contract = state.contract;
+
if (!contract || !account) {
return;
}
name = name.toLowerCase();
-
- const proposeReverse = contract.functions.find((f) => f.name === 'proposeReverse');
-
dispatch(start('propose', name, address));
- const options = {
- from: account.address
- };
- const values = [
- name,
- address
- ];
+ return getOwner(contract, name)
+ .then((owner) => {
+ if (owner.toLowerCase() !== account.address.toLowerCase()) {
+ throw new Error(`you are not the owner of "${name}"`);
+ }
- postTx(api, proposeReverse, options, values)
+ const { proposeReverse } = contract.instance;
+
+ const options = {
+ from: account.address
+ };
+
+ const values = [
+ name,
+ address
+ ];
+
+ return postTx(api, proposeReverse, options, values);
+ })
.then((txHash) => {
dispatch(success('propose'));
})
.catch((err) => {
- console.error(`could not propose reverse ${name} for address ${address}`);
- if (err) {
- console.error(err.stack);
+ if (err.type !== 'REQUEST_REJECTED') {
+ console.error(`error proposing ${name}`, err);
+ return dispatch(fail('propose', err));
}
+
dispatch(fail('propose'));
});
};
@@ -62,31 +76,42 @@ export const confirm = (name) => (dispatch, getState) => {
const state = getState();
const account = state.accounts.selected;
const contract = state.contract;
+
if (!contract || !account) {
return;
}
+
name = name.toLowerCase();
-
- const confirmReverse = contract.functions.find((f) => f.name === 'confirmReverse');
-
dispatch(start('confirm', name));
- const options = {
- from: account.address
- };
- const values = [
- name
- ];
+ return getOwner(contract, name)
+ .then((owner) => {
+ if (owner.toLowerCase() !== account.address.toLowerCase()) {
+ throw new Error(`you are not the owner of "${name}"`);
+ }
- postTx(api, confirmReverse, options, values)
+ const { confirmReverse } = contract.instance;
+
+ const options = {
+ from: account.address
+ };
+
+ const values = [
+ name
+ ];
+
+ return postTx(api, confirmReverse, options, values);
+ })
.then((txHash) => {
dispatch(success('confirm'));
})
.catch((err) => {
- console.error(`could not confirm reverse ${name}`);
- if (err) {
- console.error(err.stack);
+ if (err.type !== 'REQUEST_REJECTED') {
+ console.error(`error confirming ${name}`, err);
+ return dispatch(fail('confirm', err));
}
+
dispatch(fail('confirm'));
});
};
+
diff --git a/js/src/dapps/registry/Reverse/reducers.js b/js/src/dapps/registry/Reverse/reducers.js
index 53a242c3b..f7ba65648 100644
--- a/js/src/dapps/registry/Reverse/reducers.js
+++ b/js/src/dapps/registry/Reverse/reducers.js
@@ -17,24 +17,37 @@
import { isAction, isStage } from '../util/actions';
const initialState = {
+ error: null,
pending: false,
queue: []
};
export default (state = initialState, action) => {
+ switch (action.type) {
+ case 'clearError':
+ return {
+ ...state,
+ error: null
+ };
+ }
+
if (isAction('reverse', 'propose', action)) {
if (isStage('start', action)) {
return {
...state, pending: true,
+ error: null,
queue: state.queue.concat({
action: 'propose',
name: action.name,
address: action.address
})
};
- } else if (isStage('success', action) || isStage('fail', action)) {
+ }
+
+ if (isStage('success', action) || isStage('fail', action)) {
return {
...state, pending: false,
+ error: action.error || null,
queue: state.queue.filter((e) =>
e.action === 'propose' &&
e.name === action.name &&
@@ -48,14 +61,18 @@ export default (state = initialState, action) => {
if (isStage('start', action)) {
return {
...state, pending: true,
+ error: null,
queue: state.queue.concat({
action: 'confirm',
name: action.name
})
};
- } else if (isStage('success', action) || isStage('fail', action)) {
+ }
+
+ if (isStage('success', action) || isStage('fail', action)) {
return {
...state, pending: false,
+ error: action.error || null,
queue: state.queue.filter((e) =>
e.action === 'confirm' &&
e.name === action.name
diff --git a/js/src/dapps/registry/Reverse/reverse.css b/js/src/dapps/registry/Reverse/reverse.css
index 0b75bfaf4..b7e5f64cb 100644
--- a/js/src/dapps/registry/Reverse/reverse.css
+++ b/js/src/dapps/registry/Reverse/reverse.css
@@ -37,3 +37,6 @@
flex-shrink: 0;
}
+.error {
+ color: red;
+}
diff --git a/js/src/dapps/registry/Reverse/reverse.js b/js/src/dapps/registry/Reverse/reverse.js
index 24af0a7a4..0216d00a2 100644
--- a/js/src/dapps/registry/Reverse/reverse.js
+++ b/js/src/dapps/registry/Reverse/reverse.js
@@ -21,17 +21,20 @@ import {
Card, CardHeader, CardText, TextField, DropDownMenu, MenuItem, RaisedButton
} from 'material-ui';
+import { nullableProptype } from '~/util/proptypes';
import { AddIcon, CheckIcon } from '~/ui/Icons';
-import { propose, confirm } from './actions';
+import { clearError, confirm, propose } from './actions';
import styles from './reverse.css';
class Reverse extends Component {
static propTypes = {
+ error: nullableProptype(PropTypes.object.isRequired),
pending: PropTypes.bool.isRequired,
queue: PropTypes.array.isRequired,
- propose: PropTypes.func.isRequired,
- confirm: PropTypes.func.isRequired
+ clearError: PropTypes.func.isRequired,
+ confirm: PropTypes.func.isRequired,
+ propose: PropTypes.func.isRequired
}
state = {
@@ -77,6 +80,7 @@ class Reverse extends Component {
{ explanation }
+ { this.renderError() }
+ { error.message }
+
+ );
+ }
+
onNameChange = (e) => {
this.setState({ name: e.target.value });
};
@@ -129,9 +147,15 @@ class Reverse extends Component {
this.props.confirm(name);
}
};
+
+ clearError = () => {
+ if (this.props.error) {
+ this.props.clearError();
+ }
+ };
}
const mapStateToProps = (state) => state.reverse;
-const mapDispatchToProps = (dispatch) => bindActionCreators({ propose, confirm }, dispatch);
+const mapDispatchToProps = (dispatch) => bindActionCreators({ clearError, confirm, propose }, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(Reverse);
diff --git a/js/src/dapps/registry/ui/address.js b/js/src/dapps/registry/ui/address.js
index e3eac2c97..d8e98c220 100644
--- a/js/src/dapps/registry/ui/address.js
+++ b/js/src/dapps/registry/ui/address.js
@@ -20,31 +20,48 @@ import { connect } from 'react-redux';
import Hash from './hash';
import etherscanUrl from '../util/etherscan-url';
import IdentityIcon from '../IdentityIcon';
+import { nullableProptype } from '~/util/proptypes';
import styles from './address.css';
class Address extends Component {
static propTypes = {
address: PropTypes.string.isRequired,
- accounts: PropTypes.object.isRequired,
- contacts: PropTypes.object.isRequired,
+ account: nullableProptype(PropTypes.object.isRequired),
isTestnet: PropTypes.bool.isRequired,
key: PropTypes.string,
shortenHash: PropTypes.bool
- }
+ };
static defaultProps = {
key: 'address',
shortenHash: true
- }
+ };
render () {
- const { address, accounts, contacts, isTestnet, key, shortenHash } = this.props;
+ const { address, key } = this.props;
- let caption;
- if (accounts[address] || contacts[address]) {
- const name = (accounts[address] || contacts[address] || {}).name;
- caption = (
+ return (
+
+
+ { this.renderCaption() }
+
+ );
+ }
+
+ renderCaption () {
+ const { address, account, isTestnet, shortenHash } = this.props;
+
+ if (account) {
+ const { name } = account;
+
+ return (
);
- } else {
- caption = (
-
- { shortenHash ? (
-
- ) : address }
-
- );
}
return (
-
-
- { caption }
-
+
+ { shortenHash ? (
+
+ ) : address }
+
);
}
}
+function mapStateToProps (initState, initProps) {
+ const { accounts, contacts } = initState;
+
+ const allAccounts = Object.assign({}, accounts.all, contacts);
+
+ // Add lower case addresses to map
+ Object
+ .keys(allAccounts)
+ .forEach((address) => {
+ allAccounts[address.toLowerCase()] = allAccounts[address];
+ });
+
+ return (state, props) => {
+ const { isTestnet } = state;
+ const { address = '' } = props;
+
+ const account = allAccounts[address] || null;
+
+ return {
+ account,
+ isTestnet
+ };
+ };
+}
+
export default connect(
- // mapStateToProps
- (state) => ({
- accounts: state.accounts.all,
- contacts: state.contacts,
- isTestnet: state.isTestnet
- }),
- // mapDispatchToProps
- null
+ mapStateToProps
)(Address);
diff --git a/js/src/dapps/registry/ui/image.js b/js/src/dapps/registry/ui/image.js
index c66e34128..c7774bfac 100644
--- a/js/src/dapps/registry/ui/image.js
+++ b/js/src/dapps/registry/ui/image.js
@@ -23,10 +23,20 @@ const styles = {
border: '1px solid #777'
};
-export default (address) => (
-
-);
+export default (address) => {
+ if (!address || /^(0x)?0*$/.test(address)) {
+ return (
+
+ No image
+
+ );
+ }
+
+ return (
+
+ );
+};
diff --git a/js/src/dapps/registry/util/actions.js b/js/src/dapps/registry/util/actions.js
index 0f4f350fc..1ae7426de 100644
--- a/js/src/dapps/registry/util/actions.js
+++ b/js/src/dapps/registry/util/actions.js
@@ -19,7 +19,7 @@ export const isAction = (ns, type, action) => {
};
export const isStage = (stage, action) => {
- return action.type.slice(-1 - stage.length) === ` ${stage}`;
+ return (new RegExp(`${stage}$`)).test(action.type);
};
export const addToQueue = (queue, action, name) => {
@@ -27,5 +27,5 @@ export const addToQueue = (queue, action, name) => {
};
export const removeFromQueue = (queue, action, name) => {
- return queue.filter((e) => e.action === action && e.name === name);
+ return queue.filter((e) => !(e.action === action && e.name === name));
};
diff --git a/js/src/dapps/registry/util/post-tx.js b/js/src/dapps/registry/util/post-tx.js
index 84326dcab..298bbd843 100644
--- a/js/src/dapps/registry/util/post-tx.js
+++ b/js/src/dapps/registry/util/post-tx.js
@@ -24,12 +24,6 @@ const postTx = (api, method, opt = {}, values = []) => {
})
.then((reqId) => {
return api.pollMethod('parity_checkRequest', reqId);
- })
- .catch((err) => {
- if (err && err.type === 'REQUEST_REJECTED') {
- throw new Error('The request has been rejected.');
- }
- throw err;
});
};
diff --git a/js/src/dapps/registry/util/registry.js b/js/src/dapps/registry/util/registry.js
new file mode 100644
index 000000000..371b29aec
--- /dev/null
+++ b/js/src/dapps/registry/util/registry.js
@@ -0,0 +1,37 @@
+// 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 getOwner = (contract, name) => {
+ const { address, api } = contract;
+
+ const key = api.util.sha3(name) + '0000000000000000000000000000000000000000000000000000000000000001';
+ const position = api.util.sha3(key, { encoding: 'hex' });
+
+ return api
+ .eth
+ .getStorageAt(address, position)
+ .then((result) => {
+ if (/^(0x)?0*$/.test(result)) {
+ return '';
+ }
+
+ return '0x' + result.slice(-40);
+ });
+};
+
+export const isOwned = (contract, name) => {
+ return getOwner(contract, name).then((owner) => !!owner);
+};
diff --git a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
index 02b7ef266..99bd1c5f3 100644
--- a/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
+++ b/js/src/views/Signer/components/TransactionPendingForm/TransactionPendingFormConfirm/transactionPendingFormConfirm.js
@@ -20,6 +20,7 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import RaisedButton from 'material-ui/RaisedButton';
import ReactTooltip from 'react-tooltip';
+import keycode from 'keycode';
import { Form, Input, IdentityIcon } from '~/ui';
@@ -207,7 +208,9 @@ class TransactionPendingFormConfirm extends Component {
}
onKeyDown = (event) => {
- if (event.which !== 13) {
+ const codeName = keycode(event);
+
+ if (codeName !== 'enter') {
return;
}
diff --git a/js/src/views/Wallet/wallet.js b/js/src/views/Wallet/wallet.js
index 5fe6c957e..5418448b4 100644
--- a/js/src/views/Wallet/wallet.js
+++ b/js/src/views/Wallet/wallet.js
@@ -71,7 +71,7 @@ class Wallet extends Component {
owned: PropTypes.bool.isRequired,
setVisibleAccounts: PropTypes.func.isRequired,
wallet: PropTypes.object.isRequired,
- walletAccount: nullableProptype(PropTypes.object).isRequired
+ walletAccount: nullableProptype(PropTypes.object.isRequired)
};
state = {