Add ownership checks the Registry dApp (#4001)
* Fixes to the Registry dApp * WIP Add Owner Lookup * Proper sha3 implementation * Add working owner lookup to reg dApp * Add errors to Name Reg * Add records error in Reg dApp * Add errors for reverse in reg dApp * PR Grumbles
This commit is contained in:
parent
4c532f9e3d
commit
63017268ad
@ -138,6 +138,7 @@
|
|||||||
"blockies": "0.0.2",
|
"blockies": "0.0.2",
|
||||||
"brace": "0.9.0",
|
"brace": "0.9.0",
|
||||||
"bytes": "2.4.0",
|
"bytes": "2.4.0",
|
||||||
|
"crypto-js": "3.1.9-1",
|
||||||
"debounce": "1.0.0",
|
"debounce": "1.0.0",
|
||||||
"es6-error": "4.0.0",
|
"es6-error": "4.0.0",
|
||||||
"es6-promise": "4.0.5",
|
"es6-promise": "4.0.5",
|
||||||
|
@ -14,8 +14,21 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
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) {
|
export function sha3 (value, options) {
|
||||||
return `0x${keccak_256(value)}`;
|
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}`;
|
||||||
}
|
}
|
||||||
|
@ -34,3 +34,16 @@ ReactDOM.render(
|
|||||||
</Provider>,
|
</Provider>,
|
||||||
document.querySelector('#container')
|
document.querySelector('#container')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept('./registry/Container', () => {
|
||||||
|
require('./registry/Container');
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={ store }>
|
||||||
|
<Container />
|
||||||
|
</Provider>,
|
||||||
|
document.querySelector('#container')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -44,8 +44,8 @@ export default class Application extends Component {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
accounts: PropTypes.object.isRequired,
|
accounts: PropTypes.object.isRequired,
|
||||||
contract: nullableProptype(PropTypes.object).isRequired,
|
contract: nullableProptype(PropTypes.object.isRequired),
|
||||||
fee: nullableProptype(PropTypes.object).isRequired
|
fee: nullableProptype(PropTypes.object.isRequired)
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { sha3 } from '../parity.js';
|
import { sha3 } from '../parity.js';
|
||||||
|
import { getOwner } from '../util/registry';
|
||||||
|
|
||||||
export const clear = () => ({ type: 'lookup clear' });
|
export const clear = () => ({ type: 'lookup clear' });
|
||||||
|
|
||||||
export const lookupStart = (name, key) => ({ type: 'lookup start', name, key });
|
export const lookupStart = (name, key) => ({ type: 'lookup start', name, key });
|
||||||
export const reverseLookupStart = (address) => ({ type: 'reverseLookup start', address });
|
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 });
|
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();
|
const { contract } = getState();
|
||||||
|
|
||||||
if (!contract) {
|
if (!contract) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reverse = contract.functions
|
dispatch(reverseLookupStart(lookupAddress));
|
||||||
.find((f) => f.name === 'reverse');
|
|
||||||
|
|
||||||
dispatch(reverseLookupStart(address));
|
contract.instance
|
||||||
|
.reverse
|
||||||
reverse.call({}, [ address ])
|
.call({}, [ lookupAddress ])
|
||||||
.then((address) => dispatch(success('reverseLookup', address)))
|
.then((address) => {
|
||||||
|
dispatch(success('reverseLookup', address));
|
||||||
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(`could not lookup reverse for ${address}`);
|
console.error(`could not lookup reverse for ${lookupAddress}`);
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err.stack);
|
console.error(err.stack);
|
||||||
}
|
}
|
||||||
dispatch(fail('reverseLookup'));
|
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'));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -23,13 +23,14 @@ import DropDownMenu from 'material-ui/DropDownMenu';
|
|||||||
import MenuItem from 'material-ui/MenuItem';
|
import MenuItem from 'material-ui/MenuItem';
|
||||||
import RaisedButton from 'material-ui/RaisedButton';
|
import RaisedButton from 'material-ui/RaisedButton';
|
||||||
import SearchIcon from 'material-ui/svg-icons/action/search';
|
import SearchIcon from 'material-ui/svg-icons/action/search';
|
||||||
|
import keycode from 'keycode';
|
||||||
|
|
||||||
import { nullableProptype } from '~/util/proptypes';
|
import { nullableProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import Address from '../ui/address.js';
|
import Address from '../ui/address.js';
|
||||||
import renderImage from '../ui/image.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';
|
import styles from './lookup.css';
|
||||||
|
|
||||||
class Lookup extends Component {
|
class Lookup extends Component {
|
||||||
@ -39,6 +40,7 @@ class Lookup extends Component {
|
|||||||
|
|
||||||
clear: PropTypes.func.isRequired,
|
clear: PropTypes.func.isRequired,
|
||||||
lookup: PropTypes.func.isRequired,
|
lookup: PropTypes.func.isRequired,
|
||||||
|
ownerLookup: PropTypes.func.isRequired,
|
||||||
reverseLookup: PropTypes.func.isRequired
|
reverseLookup: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,33 +52,6 @@ class Lookup extends Component {
|
|||||||
const { input, type } = this.state;
|
const { input, type } = this.state;
|
||||||
const { result } = this.props;
|
const { result } = this.props;
|
||||||
|
|
||||||
let output = '';
|
|
||||||
if (result) {
|
|
||||||
if (type === 'A') {
|
|
||||||
output = (
|
|
||||||
<code>
|
|
||||||
<Address
|
|
||||||
address={ result }
|
|
||||||
shortenHash={ false }
|
|
||||||
/>
|
|
||||||
</code>
|
|
||||||
);
|
|
||||||
} else if (type === 'IMG') {
|
|
||||||
output = renderImage(result);
|
|
||||||
} else if (type === 'CONTENT') {
|
|
||||||
output = (
|
|
||||||
<div>
|
|
||||||
<code>{ result }</code>
|
|
||||||
<p>Keep in mind that this is most likely the hash of the content you are looking for.</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
output = (
|
|
||||||
<code>{ result }</code>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={ styles.lookup }>
|
<Card className={ styles.lookup }>
|
||||||
<CardHeader title={ 'Query the Registry' } />
|
<CardHeader title={ 'Query the Registry' } />
|
||||||
@ -85,6 +60,7 @@ class Lookup extends Component {
|
|||||||
hintText={ type === 'reverse' ? 'address' : 'name' }
|
hintText={ type === 'reverse' ? 'address' : 'name' }
|
||||||
value={ input }
|
value={ input }
|
||||||
onChange={ this.onInputChange }
|
onChange={ this.onInputChange }
|
||||||
|
onKeyDown={ this.onKeyDown }
|
||||||
/>
|
/>
|
||||||
<DropDownMenu
|
<DropDownMenu
|
||||||
value={ type }
|
value={ type }
|
||||||
@ -94,6 +70,7 @@ class Lookup extends Component {
|
|||||||
<MenuItem value='IMG' primaryText='IMG – hash of a picture in the blockchain' />
|
<MenuItem value='IMG' primaryText='IMG – hash of a picture in the blockchain' />
|
||||||
<MenuItem value='CONTENT' primaryText='CONTENT – hash of a data in the blockchain' />
|
<MenuItem value='CONTENT' primaryText='CONTENT – hash of a data in the blockchain' />
|
||||||
<MenuItem value='reverse' primaryText='reverse – find a name for an address' />
|
<MenuItem value='reverse' primaryText='reverse – find a name for an address' />
|
||||||
|
<MenuItem value='owner' primaryText='owner – find a the owner' />
|
||||||
</DropDownMenu>
|
</DropDownMenu>
|
||||||
<RaisedButton
|
<RaisedButton
|
||||||
label='Lookup'
|
label='Lookup'
|
||||||
@ -102,35 +79,102 @@ class Lookup extends Component {
|
|||||||
onTouchTap={ this.onLookupClick }
|
onTouchTap={ this.onLookupClick }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<CardText>{ output }</CardText>
|
<CardText>
|
||||||
|
{ this.renderOutput(type, result) }
|
||||||
|
</CardText>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderOutput (type, result) {
|
||||||
|
if (result === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'A') {
|
||||||
|
return (
|
||||||
|
<code>
|
||||||
|
<Address
|
||||||
|
address={ result }
|
||||||
|
shortenHash={ false }
|
||||||
|
/>
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'owner') {
|
||||||
|
if (!result) {
|
||||||
|
return (
|
||||||
|
<code>Not reserved yet</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<code>
|
||||||
|
<Address
|
||||||
|
address={ result }
|
||||||
|
shortenHash={ false }
|
||||||
|
/>
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'IMG') {
|
||||||
|
return renderImage(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'CONTENT') {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<code>{ result }</code>
|
||||||
|
<p>Keep in mind that this is most likely the hash of the content you are looking for.</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<code>{ result || 'No data' }</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onInputChange = (e) => {
|
onInputChange = (e) => {
|
||||||
this.setState({ input: e.target.value });
|
this.setState({ input: e.target.value });
|
||||||
};
|
}
|
||||||
|
|
||||||
|
onKeyDown = (event) => {
|
||||||
|
const codeName = keycode(event);
|
||||||
|
|
||||||
|
if (codeName !== 'enter') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onLookupClick();
|
||||||
|
}
|
||||||
|
|
||||||
onTypeChange = (e, i, type) => {
|
onTypeChange = (e, i, type) => {
|
||||||
this.setState({ type });
|
this.setState({ type });
|
||||||
this.props.clear();
|
this.props.clear();
|
||||||
};
|
}
|
||||||
|
|
||||||
onLookupClick = () => {
|
onLookupClick = () => {
|
||||||
const { input, type } = this.state;
|
const { input, type } = this.state;
|
||||||
|
|
||||||
if (type === 'reverse') {
|
if (type === 'reverse') {
|
||||||
this.props.reverseLookup(input);
|
return this.props.reverseLookup(input);
|
||||||
} else {
|
}
|
||||||
this.props.lookup(input, type);
|
|
||||||
|
if (type === 'owner') {
|
||||||
|
return this.props.ownerLookup(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.lookup(input, type);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => state.lookup;
|
const mapStateToProps = (state) => state.lookup;
|
||||||
const mapDispatchToProps = (dispatch) =>
|
const mapDispatchToProps = (dispatch) =>
|
||||||
bindActionCreators({
|
bindActionCreators({
|
||||||
clear, lookup, reverseLookup
|
clear, lookup, ownerLookup, reverseLookup
|
||||||
}, dispatch);
|
}, dispatch);
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Lookup);
|
export default connect(mapStateToProps, mapDispatchToProps)(Lookup);
|
||||||
|
@ -24,7 +24,7 @@ const initialState = {
|
|||||||
export default (state = initialState, action) => {
|
export default (state = initialState, action) => {
|
||||||
const { type } = action;
|
const { type } = action;
|
||||||
|
|
||||||
if (type.slice(0, 7) !== 'lookup ' && type.slice(0, 14) !== 'reverseLookup ') {
|
if (!/^(lookup|reverseLookup|ownerLookup)/.test(type)) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,8 +15,13 @@
|
|||||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import { sha3, api } from '../parity.js';
|
import { sha3, api } from '../parity.js';
|
||||||
|
import { getOwner, isOwned } from '../util/registry';
|
||||||
import postTx from '../util/post-tx';
|
import postTx from '../util/post-tx';
|
||||||
|
|
||||||
|
export const clearError = () => ({
|
||||||
|
type: 'clearError'
|
||||||
|
});
|
||||||
|
|
||||||
const alreadyQueued = (queue, action, name) =>
|
const alreadyQueued = (queue, action, name) =>
|
||||||
!!queue.find((entry) => entry.action === action && entry.name === 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 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) => {
|
export const reserve = (name) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const account = state.accounts.selected;
|
const account = state.accounts.selected;
|
||||||
const contract = state.contract;
|
const contract = state.contract;
|
||||||
const fee = state.fee;
|
const fee = state.fee;
|
||||||
|
|
||||||
if (!contract || !account) {
|
if (!contract || !account) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -40,10 +46,17 @@ export const reserve = (name) => (dispatch, getState) => {
|
|||||||
if (alreadyQueued(state.names.queue, 'reserve', name)) {
|
if (alreadyQueued(state.names.queue, 'reserve', name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const reserve = contract.functions.find((f) => f.name === 'reserve');
|
|
||||||
|
|
||||||
dispatch(reserveStart(name));
|
dispatch(reserveStart(name));
|
||||||
|
|
||||||
|
return isOwned(contract, name)
|
||||||
|
.then((owned) => {
|
||||||
|
if (owned) {
|
||||||
|
throw new Error(`"${name}" has already been reserved`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { reserve } = contract.instance;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
from: account.address,
|
from: account.address,
|
||||||
value: fee
|
value: fee
|
||||||
@ -52,15 +65,15 @@ export const reserve = (name) => (dispatch, getState) => {
|
|||||||
sha3(name)
|
sha3(name)
|
||||||
];
|
];
|
||||||
|
|
||||||
postTx(api, reserve, options, values)
|
return postTx(api, reserve, options, values);
|
||||||
|
})
|
||||||
.then((txHash) => {
|
.then((txHash) => {
|
||||||
dispatch(reserveSuccess(name));
|
dispatch(reserveSuccess(name));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(`could not reserve ${name}`);
|
if (err.type !== 'REQUEST_REJECTED') {
|
||||||
|
console.error(`error rerserving ${name}`, err);
|
||||||
if (err) {
|
return dispatch(reserveFail(name, err));
|
||||||
console.error(err.stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(reserveFail(name));
|
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 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) => {
|
export const drop = (name) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const account = state.accounts.selected;
|
const account = state.accounts.selected;
|
||||||
const contract = state.contract;
|
const contract = state.contract;
|
||||||
|
|
||||||
if (!contract || !account) {
|
if (!contract || !account) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = name.toLowerCase();
|
name = name.toLowerCase();
|
||||||
|
|
||||||
if (alreadyQueued(state.names.queue, 'drop', name)) {
|
if (alreadyQueued(state.names.queue, 'drop', name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const drop = contract.functions.find((f) => f.name === 'drop');
|
|
||||||
|
|
||||||
dispatch(dropStart(name));
|
dispatch(dropStart(name));
|
||||||
|
|
||||||
|
return getOwner(contract, name)
|
||||||
|
.then((owner) => {
|
||||||
|
if (owner.toLowerCase() !== account.address.toLowerCase()) {
|
||||||
|
throw new Error(`you are not the owner of "${name}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { drop } = contract.instance;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
from: account.address
|
from: account.address
|
||||||
};
|
};
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
sha3(name)
|
sha3(name)
|
||||||
];
|
];
|
||||||
|
|
||||||
postTx(api, drop, options, values)
|
return postTx(api, drop, options, values);
|
||||||
|
})
|
||||||
.then((txhash) => {
|
.then((txhash) => {
|
||||||
dispatch(dropSuccess(name));
|
dispatch(dropSuccess(name));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(`could not drop ${name}`);
|
if (err.type !== 'REQUEST_REJECTED') {
|
||||||
|
console.error(`error dropping ${name}`, err);
|
||||||
if (err) {
|
return dispatch(dropFail(name, err));
|
||||||
console.error(err.stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(reserveFail(name));
|
dispatch(dropFail(name));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -35,7 +35,12 @@
|
|||||||
.link {
|
.link {
|
||||||
color: #00BCD4;
|
color: #00BCD4;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
|
||||||
.link:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
@ -24,9 +24,10 @@ import MenuItem from 'material-ui/MenuItem';
|
|||||||
import RaisedButton from 'material-ui/RaisedButton';
|
import RaisedButton from 'material-ui/RaisedButton';
|
||||||
import CheckIcon from 'material-ui/svg-icons/navigation/check';
|
import CheckIcon from 'material-ui/svg-icons/navigation/check';
|
||||||
|
|
||||||
|
import { nullableProptype } from '~/util/proptypes';
|
||||||
import { fromWei } from '../parity.js';
|
import { fromWei } from '../parity.js';
|
||||||
|
|
||||||
import { reserve, drop } from './actions';
|
import { clearError, reserve, drop } from './actions';
|
||||||
import styles from './names.css';
|
import styles from './names.css';
|
||||||
|
|
||||||
const useSignerText = (<p>Use the <a href='/#/signer' className={ styles.link } target='_blank'>Signer</a> to authenticate the following changes.</p>);
|
const useSignerText = (<p>Use the <a href='/#/signer' className={ styles.link } target='_blank'>Signer</a> to authenticate the following changes.</p>);
|
||||||
@ -78,35 +79,21 @@ const renderQueue = (queue) => {
|
|||||||
class Names extends Component {
|
class Names extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
error: nullableProptype(PropTypes.object.isRequired),
|
||||||
fee: PropTypes.object.isRequired,
|
fee: PropTypes.object.isRequired,
|
||||||
pending: PropTypes.bool.isRequired,
|
pending: PropTypes.bool.isRequired,
|
||||||
queue: PropTypes.array.isRequired,
|
queue: PropTypes.array.isRequired,
|
||||||
|
|
||||||
|
clearError: PropTypes.func.isRequired,
|
||||||
reserve: PropTypes.func.isRequired,
|
reserve: PropTypes.func.isRequired,
|
||||||
drop: PropTypes.func.isRequired
|
drop: PropTypes.func.isRequired
|
||||||
}
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
action: 'reserve',
|
action: 'reserve',
|
||||||
name: ''
|
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 () {
|
render () {
|
||||||
const { action, name } = this.state;
|
const { action, name } = this.state;
|
||||||
const { fee, pending, queue } = this.props;
|
const { fee, pending, queue } = this.props;
|
||||||
@ -122,6 +109,7 @@ class Names extends Component {
|
|||||||
: (<p className={ styles.noSpacing }>To drop a name, you have to be the owner.</p>)
|
: (<p className={ styles.noSpacing }>To drop a name, you have to be the owner.</p>)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
{ this.renderError() }
|
||||||
<div className={ styles.box }>
|
<div className={ styles.box }>
|
||||||
<TextField
|
<TextField
|
||||||
hintText='name'
|
hintText='name'
|
||||||
@ -154,23 +142,50 @@ class Names extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderError () {
|
||||||
|
const { error } = this.props;
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.error }>
|
||||||
|
<code>{ error.message }</code>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onNameChange = (e) => {
|
onNameChange = (e) => {
|
||||||
|
this.clearError();
|
||||||
this.setState({ name: e.target.value });
|
this.setState({ name: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onActionChange = (e, i, action) => {
|
onActionChange = (e, i, action) => {
|
||||||
|
this.clearError();
|
||||||
this.setState({ action });
|
this.setState({ action });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmitClick = () => {
|
onSubmitClick = () => {
|
||||||
const { action, name } = this.state;
|
const { action, name } = this.state;
|
||||||
|
|
||||||
if (action === 'reserve') {
|
if (action === 'reserve') {
|
||||||
this.props.reserve(name);
|
return this.props.reserve(name);
|
||||||
} else if (action === 'drop') {
|
}
|
||||||
this.props.drop(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 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);
|
export default connect(mapStateToProps, mapDispatchToProps)(Names);
|
||||||
|
@ -17,32 +17,55 @@
|
|||||||
import { isAction, isStage, addToQueue, removeFromQueue } from '../util/actions';
|
import { isAction, isStage, addToQueue, removeFromQueue } from '../util/actions';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
error: null,
|
||||||
pending: false,
|
pending: false,
|
||||||
queue: []
|
queue: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (state = initialState, action) => {
|
export default (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'clearError':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (isAction('names', 'reserve', action)) {
|
if (isAction('names', 'reserve', action)) {
|
||||||
if (isStage('start', action)) {
|
if (isStage('start', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: true,
|
...state,
|
||||||
|
error: null,
|
||||||
|
pending: true,
|
||||||
queue: addToQueue(state.queue, 'reserve', action.name)
|
queue: addToQueue(state.queue, 'reserve', action.name)
|
||||||
};
|
};
|
||||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
}
|
||||||
|
|
||||||
|
if (isStage('success', action) || isStage('fail', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: false,
|
...state,
|
||||||
|
error: action.error || null,
|
||||||
|
pending: false,
|
||||||
queue: removeFromQueue(state.queue, 'reserve', action.name)
|
queue: removeFromQueue(state.queue, 'reserve', action.name)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (isAction('names', 'drop', action)) {
|
}
|
||||||
|
|
||||||
|
if (isAction('names', 'drop', action)) {
|
||||||
if (isStage('start', action)) {
|
if (isStage('start', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: true,
|
...state,
|
||||||
|
error: null,
|
||||||
|
pending: true,
|
||||||
queue: addToQueue(state.queue, 'drop', action.name)
|
queue: addToQueue(state.queue, 'drop', action.name)
|
||||||
};
|
};
|
||||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
}
|
||||||
|
|
||||||
|
if (isStage('success', action) || isStage('fail', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: false,
|
...state,
|
||||||
|
error: action.error || null,
|
||||||
|
pending: false,
|
||||||
queue: removeFromQueue(state.queue, 'drop', action.name)
|
queue: removeFromQueue(state.queue, 'drop', action.name)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,45 +16,57 @@
|
|||||||
|
|
||||||
import { sha3, api } from '../parity.js';
|
import { sha3, api } from '../parity.js';
|
||||||
import postTx from '../util/post-tx';
|
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 start = (name, key, value) => ({ type: 'records update start', name, key, value });
|
||||||
|
|
||||||
export const success = () => ({ type: 'records update success' });
|
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) => {
|
export const update = (name, key, value) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const account = state.accounts.selected;
|
const account = state.accounts.selected;
|
||||||
const contract = state.contract;
|
const contract = state.contract;
|
||||||
|
|
||||||
if (!contract || !account) {
|
if (!contract || !account) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = name.toLowerCase();
|
name = name.toLowerCase();
|
||||||
|
dispatch(start(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}"`);
|
||||||
|
}
|
||||||
|
|
||||||
const fnName = key === 'A' ? 'setAddress' : 'set';
|
const fnName = key === 'A' ? 'setAddress' : 'set';
|
||||||
const setAddress = contract.functions.find((f) => f.name === fnName);
|
const method = contract.instance[fnName];
|
||||||
|
|
||||||
dispatch(start(name, key, value));
|
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
from: account.address
|
from: account.address
|
||||||
};
|
};
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
sha3(name),
|
sha3(name),
|
||||||
key,
|
key,
|
||||||
value
|
value
|
||||||
];
|
];
|
||||||
|
|
||||||
postTx(api, setAddress, options, values)
|
return postTx(api, method, options, values);
|
||||||
|
})
|
||||||
.then((txHash) => {
|
.then((txHash) => {
|
||||||
dispatch(success());
|
dispatch(success());
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.error(`could not update ${key} record of ${name}`);
|
if (err.type !== 'REQUEST_REJECTED') {
|
||||||
|
console.error(`error updating ${name}`, err);
|
||||||
if (err) {
|
return dispatch(fail(err));
|
||||||
console.error(err.stack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fail());
|
dispatch(fail());
|
||||||
|
@ -36,3 +36,7 @@
|
|||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
@ -24,17 +24,20 @@ import MenuItem from 'material-ui/MenuItem';
|
|||||||
import RaisedButton from 'material-ui/RaisedButton';
|
import RaisedButton from 'material-ui/RaisedButton';
|
||||||
import SaveIcon from 'material-ui/svg-icons/content/save';
|
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';
|
import styles from './records.css';
|
||||||
|
|
||||||
class Records extends Component {
|
class Records extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
error: nullableProptype(PropTypes.object.isRequired),
|
||||||
pending: PropTypes.bool.isRequired,
|
pending: PropTypes.bool.isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
|
|
||||||
|
clearError: PropTypes.func.isRequired,
|
||||||
update: PropTypes.func.isRequired
|
update: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,6 +56,7 @@ class Records extends Component {
|
|||||||
<p className={ styles.noSpacing }>
|
<p className={ styles.noSpacing }>
|
||||||
You can only modify entries of names that you previously registered.
|
You can only modify entries of names that you previously registered.
|
||||||
</p>
|
</p>
|
||||||
|
{ this.renderError() }
|
||||||
<div className={ styles.box }>
|
<div className={ styles.box }>
|
||||||
<TextField
|
<TextField
|
||||||
hintText='name'
|
hintText='name'
|
||||||
@ -88,22 +92,46 @@ class Records extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderError () {
|
||||||
|
const { error } = this.props;
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.error }>
|
||||||
|
<code>{ error.message }</code>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onNameChange = (e) => {
|
onNameChange = (e) => {
|
||||||
|
this.clearError();
|
||||||
this.setState({ name: e.target.value });
|
this.setState({ name: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onTypeChange = (e, i, type) => {
|
onTypeChange = (e, i, type) => {
|
||||||
this.setState({ type });
|
this.setState({ type });
|
||||||
};
|
};
|
||||||
|
|
||||||
onValueChange = (e) => {
|
onValueChange = (e) => {
|
||||||
this.setState({ value: e.target.value });
|
this.setState({ value: e.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
onSaveClick = () => {
|
onSaveClick = () => {
|
||||||
const { name, type, value } = this.state;
|
const { name, type, value } = this.state;
|
||||||
this.props.update(name, type, value);
|
this.props.update(name, type, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
clearError = () => {
|
||||||
|
if (this.props.error) {
|
||||||
|
this.props.clearError();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => state.records;
|
const mapStateToProps = (state) => state.records;
|
||||||
const mapDispatchToProps = (dispatch) => bindActionCreators({ update }, dispatch);
|
const mapDispatchToProps = (dispatch) => bindActionCreators({ clearError, update }, dispatch);
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Records);
|
export default connect(mapStateToProps, mapDispatchToProps)(Records);
|
||||||
|
@ -17,11 +17,20 @@
|
|||||||
import { isAction, isStage } from '../util/actions';
|
import { isAction, isStage } from '../util/actions';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
error: null,
|
||||||
pending: false,
|
pending: false,
|
||||||
name: '', type: '', value: ''
|
name: '', type: '', value: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (state = initialState, action) => {
|
export default (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'clearError':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (!isAction('records', 'update', action)) {
|
if (!isAction('records', 'update', action)) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@ -29,11 +38,15 @@ export default (state = initialState, action) => {
|
|||||||
if (isStage('start', action)) {
|
if (isStage('start', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: true,
|
...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 {
|
return {
|
||||||
...state, pending: false,
|
...state, pending: false,
|
||||||
|
error: action.error || null,
|
||||||
name: initialState.name, type: initialState.type, value: initialState.value
|
name: initialState.name, type: initialState.type, value: initialState.value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -16,44 +16,58 @@
|
|||||||
|
|
||||||
import { api } from '../parity.js';
|
import { api } from '../parity.js';
|
||||||
import postTx from '../util/post-tx';
|
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 start = (action, name, address) => ({ type: `reverse ${action} start`, name, address });
|
||||||
|
|
||||||
export const success = (action) => ({ type: `reverse ${action} success` });
|
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) => {
|
export const propose = (name, address) => (dispatch, getState) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const account = state.accounts.selected;
|
const account = state.accounts.selected;
|
||||||
const contract = state.contract;
|
const contract = state.contract;
|
||||||
|
|
||||||
if (!contract || !account) {
|
if (!contract || !account) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = name.toLowerCase();
|
name = name.toLowerCase();
|
||||||
|
|
||||||
const proposeReverse = contract.functions.find((f) => f.name === 'proposeReverse');
|
|
||||||
|
|
||||||
dispatch(start('propose', name, address));
|
dispatch(start('propose', name, address));
|
||||||
|
|
||||||
|
return getOwner(contract, name)
|
||||||
|
.then((owner) => {
|
||||||
|
if (owner.toLowerCase() !== account.address.toLowerCase()) {
|
||||||
|
throw new Error(`you are not the owner of "${name}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { proposeReverse } = contract.instance;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
from: account.address
|
from: account.address
|
||||||
};
|
};
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
name,
|
name,
|
||||||
address
|
address
|
||||||
];
|
];
|
||||||
|
|
||||||
postTx(api, proposeReverse, options, values)
|
return postTx(api, proposeReverse, options, values);
|
||||||
|
})
|
||||||
.then((txHash) => {
|
.then((txHash) => {
|
||||||
dispatch(success('propose'));
|
dispatch(success('propose'));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(`could not propose reverse ${name} for address ${address}`);
|
if (err.type !== 'REQUEST_REJECTED') {
|
||||||
if (err) {
|
console.error(`error proposing ${name}`, err);
|
||||||
console.error(err.stack);
|
return dispatch(fail('propose', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fail('propose'));
|
dispatch(fail('propose'));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -62,31 +76,42 @@ export const confirm = (name) => (dispatch, getState) => {
|
|||||||
const state = getState();
|
const state = getState();
|
||||||
const account = state.accounts.selected;
|
const account = state.accounts.selected;
|
||||||
const contract = state.contract;
|
const contract = state.contract;
|
||||||
|
|
||||||
if (!contract || !account) {
|
if (!contract || !account) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = name.toLowerCase();
|
name = name.toLowerCase();
|
||||||
|
|
||||||
const confirmReverse = contract.functions.find((f) => f.name === 'confirmReverse');
|
|
||||||
|
|
||||||
dispatch(start('confirm', name));
|
dispatch(start('confirm', name));
|
||||||
|
|
||||||
|
return getOwner(contract, name)
|
||||||
|
.then((owner) => {
|
||||||
|
if (owner.toLowerCase() !== account.address.toLowerCase()) {
|
||||||
|
throw new Error(`you are not the owner of "${name}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { confirmReverse } = contract.instance;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
from: account.address
|
from: account.address
|
||||||
};
|
};
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
name
|
name
|
||||||
];
|
];
|
||||||
|
|
||||||
postTx(api, confirmReverse, options, values)
|
return postTx(api, confirmReverse, options, values);
|
||||||
|
})
|
||||||
.then((txHash) => {
|
.then((txHash) => {
|
||||||
dispatch(success('confirm'));
|
dispatch(success('confirm'));
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error(`could not confirm reverse ${name}`);
|
if (err.type !== 'REQUEST_REJECTED') {
|
||||||
if (err) {
|
console.error(`error confirming ${name}`, err);
|
||||||
console.error(err.stack);
|
return dispatch(fail('confirm', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fail('confirm'));
|
dispatch(fail('confirm'));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,24 +17,37 @@
|
|||||||
import { isAction, isStage } from '../util/actions';
|
import { isAction, isStage } from '../util/actions';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
|
error: null,
|
||||||
pending: false,
|
pending: false,
|
||||||
queue: []
|
queue: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (state = initialState, action) => {
|
export default (state = initialState, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'clearError':
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (isAction('reverse', 'propose', action)) {
|
if (isAction('reverse', 'propose', action)) {
|
||||||
if (isStage('start', action)) {
|
if (isStage('start', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: true,
|
...state, pending: true,
|
||||||
|
error: null,
|
||||||
queue: state.queue.concat({
|
queue: state.queue.concat({
|
||||||
action: 'propose',
|
action: 'propose',
|
||||||
name: action.name,
|
name: action.name,
|
||||||
address: action.address
|
address: action.address
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
}
|
||||||
|
|
||||||
|
if (isStage('success', action) || isStage('fail', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: false,
|
...state, pending: false,
|
||||||
|
error: action.error || null,
|
||||||
queue: state.queue.filter((e) =>
|
queue: state.queue.filter((e) =>
|
||||||
e.action === 'propose' &&
|
e.action === 'propose' &&
|
||||||
e.name === action.name &&
|
e.name === action.name &&
|
||||||
@ -48,14 +61,18 @@ export default (state = initialState, action) => {
|
|||||||
if (isStage('start', action)) {
|
if (isStage('start', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: true,
|
...state, pending: true,
|
||||||
|
error: null,
|
||||||
queue: state.queue.concat({
|
queue: state.queue.concat({
|
||||||
action: 'confirm',
|
action: 'confirm',
|
||||||
name: action.name
|
name: action.name
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
} else if (isStage('success', action) || isStage('fail', action)) {
|
}
|
||||||
|
|
||||||
|
if (isStage('success', action) || isStage('fail', action)) {
|
||||||
return {
|
return {
|
||||||
...state, pending: false,
|
...state, pending: false,
|
||||||
|
error: action.error || null,
|
||||||
queue: state.queue.filter((e) =>
|
queue: state.queue.filter((e) =>
|
||||||
e.action === 'confirm' &&
|
e.action === 'confirm' &&
|
||||||
e.name === action.name
|
e.name === action.name
|
||||||
|
@ -37,3 +37,6 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
@ -21,17 +21,20 @@ import {
|
|||||||
Card, CardHeader, CardText, TextField, DropDownMenu, MenuItem, RaisedButton
|
Card, CardHeader, CardText, TextField, DropDownMenu, MenuItem, RaisedButton
|
||||||
} from 'material-ui';
|
} from 'material-ui';
|
||||||
|
|
||||||
|
import { nullableProptype } from '~/util/proptypes';
|
||||||
import { AddIcon, CheckIcon } from '~/ui/Icons';
|
import { AddIcon, CheckIcon } from '~/ui/Icons';
|
||||||
import { propose, confirm } from './actions';
|
import { clearError, confirm, propose } from './actions';
|
||||||
import styles from './reverse.css';
|
import styles from './reverse.css';
|
||||||
|
|
||||||
class Reverse extends Component {
|
class Reverse extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
error: nullableProptype(PropTypes.object.isRequired),
|
||||||
pending: PropTypes.bool.isRequired,
|
pending: PropTypes.bool.isRequired,
|
||||||
queue: PropTypes.array.isRequired,
|
queue: PropTypes.array.isRequired,
|
||||||
|
|
||||||
propose: PropTypes.func.isRequired,
|
clearError: PropTypes.func.isRequired,
|
||||||
confirm: PropTypes.func.isRequired
|
confirm: PropTypes.func.isRequired,
|
||||||
|
propose: PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -77,6 +80,7 @@ class Reverse extends Component {
|
|||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
{ explanation }
|
{ explanation }
|
||||||
|
{ this.renderError() }
|
||||||
<div className={ styles.box }>
|
<div className={ styles.box }>
|
||||||
<DropDownMenu
|
<DropDownMenu
|
||||||
disabled={ pending }
|
disabled={ pending }
|
||||||
@ -108,6 +112,20 @@ class Reverse extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderError () {
|
||||||
|
const { error } = this.props;
|
||||||
|
|
||||||
|
if (!error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ styles.error }>
|
||||||
|
<code>{ error.message }</code>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
onNameChange = (e) => {
|
onNameChange = (e) => {
|
||||||
this.setState({ name: e.target.value });
|
this.setState({ name: e.target.value });
|
||||||
};
|
};
|
||||||
@ -129,9 +147,15 @@ class Reverse extends Component {
|
|||||||
this.props.confirm(name);
|
this.props.confirm(name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
clearError = () => {
|
||||||
|
if (this.props.error) {
|
||||||
|
this.props.clearError();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => state.reverse;
|
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);
|
export default connect(mapStateToProps, mapDispatchToProps)(Reverse);
|
||||||
|
@ -20,31 +20,48 @@ import { connect } from 'react-redux';
|
|||||||
import Hash from './hash';
|
import Hash from './hash';
|
||||||
import etherscanUrl from '../util/etherscan-url';
|
import etherscanUrl from '../util/etherscan-url';
|
||||||
import IdentityIcon from '../IdentityIcon';
|
import IdentityIcon from '../IdentityIcon';
|
||||||
|
import { nullableProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
import styles from './address.css';
|
import styles from './address.css';
|
||||||
|
|
||||||
class Address extends Component {
|
class Address extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
address: PropTypes.string.isRequired,
|
address: PropTypes.string.isRequired,
|
||||||
accounts: PropTypes.object.isRequired,
|
account: nullableProptype(PropTypes.object.isRequired),
|
||||||
contacts: PropTypes.object.isRequired,
|
|
||||||
isTestnet: PropTypes.bool.isRequired,
|
isTestnet: PropTypes.bool.isRequired,
|
||||||
key: PropTypes.string,
|
key: PropTypes.string,
|
||||||
shortenHash: PropTypes.bool
|
shortenHash: PropTypes.bool
|
||||||
}
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
key: 'address',
|
key: 'address',
|
||||||
shortenHash: true
|
shortenHash: true
|
||||||
}
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { address, accounts, contacts, isTestnet, key, shortenHash } = this.props;
|
const { address, key } = this.props;
|
||||||
|
|
||||||
let caption;
|
return (
|
||||||
if (accounts[address] || contacts[address]) {
|
<div
|
||||||
const name = (accounts[address] || contacts[address] || {}).name;
|
key={ key }
|
||||||
caption = (
|
className={ styles.container }
|
||||||
|
>
|
||||||
|
<IdentityIcon
|
||||||
|
address={ address }
|
||||||
|
className={ styles.align }
|
||||||
|
/>
|
||||||
|
{ this.renderCaption() }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCaption () {
|
||||||
|
const { address, account, isTestnet, shortenHash } = this.props;
|
||||||
|
|
||||||
|
if (account) {
|
||||||
|
const { name } = account;
|
||||||
|
|
||||||
|
return (
|
||||||
<a
|
<a
|
||||||
className={ styles.link }
|
className={ styles.link }
|
||||||
href={ etherscanUrl(address, isTestnet) }
|
href={ etherscanUrl(address, isTestnet) }
|
||||||
@ -58,8 +75,9 @@ class Address extends Component {
|
|||||||
</abbr>
|
</abbr>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
}
|
||||||
caption = (
|
|
||||||
|
return (
|
||||||
<code className={ styles.align }>
|
<code className={ styles.align }>
|
||||||
{ shortenHash ? (
|
{ shortenHash ? (
|
||||||
<Hash
|
<Hash
|
||||||
@ -70,29 +88,33 @@ class Address extends Component {
|
|||||||
</code>
|
</code>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={ key }
|
|
||||||
className={ styles.container }
|
|
||||||
>
|
|
||||||
<IdentityIcon
|
|
||||||
address={ address }
|
|
||||||
className={ styles.align }
|
|
||||||
/>
|
|
||||||
{ caption }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(
|
export default connect(
|
||||||
// mapStateToProps
|
mapStateToProps
|
||||||
(state) => ({
|
|
||||||
accounts: state.accounts.all,
|
|
||||||
contacts: state.contacts,
|
|
||||||
isTestnet: state.isTestnet
|
|
||||||
}),
|
|
||||||
// mapDispatchToProps
|
|
||||||
null
|
|
||||||
)(Address);
|
)(Address);
|
||||||
|
@ -23,10 +23,20 @@ const styles = {
|
|||||||
border: '1px solid #777'
|
border: '1px solid #777'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (address) => (
|
export default (address) => {
|
||||||
|
if (!address || /^(0x)?0*$/.test(address)) {
|
||||||
|
return (
|
||||||
|
<code>
|
||||||
|
No image
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<img
|
<img
|
||||||
src={ `${parityNode}/${address}/` }
|
src={ `${parityNode}/${address}/` }
|
||||||
alt={ address }
|
alt={ address }
|
||||||
style={ styles }
|
style={ styles }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
@ -19,7 +19,7 @@ export const isAction = (ns, type, action) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isStage = (stage, 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) => {
|
export const addToQueue = (queue, action, name) => {
|
||||||
@ -27,5 +27,5 @@ export const addToQueue = (queue, action, name) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const removeFromQueue = (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));
|
||||||
};
|
};
|
||||||
|
@ -24,12 +24,6 @@ const postTx = (api, method, opt = {}, values = []) => {
|
|||||||
})
|
})
|
||||||
.then((reqId) => {
|
.then((reqId) => {
|
||||||
return api.pollMethod('parity_checkRequest', 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;
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
37
js/src/dapps/registry/util/registry.js
Normal file
37
js/src/dapps/registry/util/registry.js
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
@ -20,6 +20,7 @@ import { connect } from 'react-redux';
|
|||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import RaisedButton from 'material-ui/RaisedButton';
|
import RaisedButton from 'material-ui/RaisedButton';
|
||||||
import ReactTooltip from 'react-tooltip';
|
import ReactTooltip from 'react-tooltip';
|
||||||
|
import keycode from 'keycode';
|
||||||
|
|
||||||
import { Form, Input, IdentityIcon } from '~/ui';
|
import { Form, Input, IdentityIcon } from '~/ui';
|
||||||
|
|
||||||
@ -207,7 +208,9 @@ class TransactionPendingFormConfirm extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown = (event) => {
|
onKeyDown = (event) => {
|
||||||
if (event.which !== 13) {
|
const codeName = keycode(event);
|
||||||
|
|
||||||
|
if (codeName !== 'enter') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ class Wallet extends Component {
|
|||||||
owned: PropTypes.bool.isRequired,
|
owned: PropTypes.bool.isRequired,
|
||||||
setVisibleAccounts: PropTypes.func.isRequired,
|
setVisibleAccounts: PropTypes.func.isRequired,
|
||||||
wallet: PropTypes.object.isRequired,
|
wallet: PropTypes.object.isRequired,
|
||||||
walletAccount: nullableProptype(PropTypes.object).isRequired
|
walletAccount: nullableProptype(PropTypes.object.isRequired)
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
Loading…
Reference in New Issue
Block a user