Requesting an SMS from the Parity server and waiting for the puzzle to be put into the contract.
);
default:
diff --git a/js/src/modals/SMSVerification/store.js b/js/src/modals/SMSVerification/store.js
index 8c4db373a..76045d814 100644
--- a/js/src/modals/SMSVerification/store.js
+++ b/js/src/modals/SMSVerification/store.js
@@ -20,19 +20,16 @@ import { sha3 } from '../../api/util/sha3';
import Contracts from '../../contracts';
-import { checkIfVerified, checkIfRequested } from '../../contracts/sms-verification';
+import { checkIfVerified, checkIfRequested, awaitPuzzle } from '../../contracts/sms-verification';
import { postToServer } from '../../3rdparty/sms-verification';
import checkIfTxFailed from '../../util/check-if-tx-failed';
import waitForConfirmations from '../../util/wait-for-block-confirmations';
-const validCode = /^[A-Z\s]+$/i;
-
export const LOADING = 'fetching-contract';
export const QUERY_DATA = 'query-data';
export const POSTING_REQUEST = 'posting-request';
export const POSTED_REQUEST = 'posted-request';
export const REQUESTING_SMS = 'requesting-sms';
-export const REQUESTED_SMS = 'requested-sms';
export const QUERY_CODE = 'query-code';
export const POSTING_CONFIRMATION = 'posting-confirmation';
export const POSTED_CONFIRMATION = 'posted-confirmation';
@@ -50,11 +47,9 @@ export default class VerificationStore {
@observable number = '';
@observable requestTx = null;
@observable code = '';
+ @observable isCodeValid = null;
@observable confirmationTx = null;
- @computed get isCodeValid () {
- return validCode.test(this.code);
- }
@computed get isNumberValid () {
return phone.isValidNumber(this.number);
}
@@ -72,20 +67,19 @@ export default class VerificationStore {
return this.contract && this.fee && this.isVerified !== null && this.hasRequested !== null;
case QUERY_DATA:
return this.isNumberValid && this.consentGiven;
- case REQUESTED_SMS:
- return this.requestTx;
case QUERY_CODE:
- return this.isCodeValid;
+ return this.requestTx && this.isCodeValid === true;
case POSTED_CONFIRMATION:
- return this.confirmationTx;
+ return !!this.confirmationTx;
default:
return false;
}
}
- constructor (api, account) {
+ constructor (api, account, isTestnet) {
this.api = api;
this.account = account;
+ this.isTestnet = isTestnet;
this.step = LOADING;
Contracts.create(api).registry.getContract('smsverification')
@@ -151,7 +145,26 @@ export default class VerificationStore {
}
@action setCode = (code) => {
+ const { contract, account } = this;
+ if (!contract || !account || code.length === 0) return;
+
+ const confirm = contract.functions.find((fn) => fn.name === 'confirm');
+ const options = { from: account };
+ const values = [ sha3(code) ];
+
this.code = code;
+ this.isCodeValid = null;
+ confirm.estimateGas(options, values)
+ .then((gas) => {
+ options.gas = gas.mul(1.2).toFixed(0);
+ return confirm.call(options, values);
+ })
+ .then((result) => {
+ this.isCodeValid = result === true;
+ })
+ .catch((err) => {
+ this.error = 'Failed to check if the code is valid: ' + err.message;
+ });
}
@action sendRequest = () => {
@@ -188,11 +201,15 @@ export default class VerificationStore {
chain
.then(() => {
- this.step = REQUESTING_SMS;
- return postToServer({ number, address: account });
+ return api.parity.netChain();
})
+ .then((chain) => {
+ this.step = REQUESTING_SMS;
+ return postToServer({ number, address: account }, this.isTestnet);
+ })
+ .then(() => awaitPuzzle(api, contract, account))
.then(() => {
- this.step = REQUESTED_SMS;
+ this.step = QUERY_CODE;
})
.catch((err) => {
this.error = 'Failed to request a confirmation SMS: ' + err.message;
diff --git a/js/src/ui/IdentityName/identityName.js b/js/src/ui/IdentityName/identityName.js
index f19e2bb2e..85ff34a35 100644
--- a/js/src/ui/IdentityName/identityName.js
+++ b/js/src/ui/IdentityName/identityName.js
@@ -18,6 +18,8 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
+import ShortenedHash from '../ShortenedHash';
+
const defaultName = 'UNNAMED';
class IdentityName extends Component {
@@ -41,7 +43,7 @@ class IdentityName extends Component {
return null;
}
- const addressFallback = shorten ? this.formatHash(address) : address;
+ const addressFallback = shorten ? () : address;
const fallback = unknown ? defaultName : addressFallback;
const isUuid = hasAccount && account.name === account.uuid;
const displayName = (name && name.toUpperCase().trim()) ||
@@ -55,14 +57,6 @@ class IdentityName extends Component {
);
}
-
- formatHash (hash) {
- if (!hash || hash.length <= 16) {
- return hash;
- }
-
- return `${hash.substr(2, 6)}...${hash.slice(-6)}`;
- }
}
function mapStateToProps (state) {
diff --git a/js/src/ui/ShortenedHash/index.js b/js/src/ui/ShortenedHash/index.js
new file mode 100644
index 000000000..4c9314386
--- /dev/null
+++ b/js/src/ui/ShortenedHash/index.js
@@ -0,0 +1,17 @@
+// Copyright 2015, 2016 Ethcore (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 default from './shortenedHash';
diff --git a/js/src/ui/ShortenedHash/shortenedHash.css b/js/src/ui/ShortenedHash/shortenedHash.css
new file mode 100644
index 000000000..7184a5fec
--- /dev/null
+++ b/js/src/ui/ShortenedHash/shortenedHash.css
@@ -0,0 +1,21 @@
+/* Copyright 2015, 2016 Ethcore (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 .
+*/
+
+.hash {
+ display: inline-block;
+ word-break: break-all;
+}
diff --git a/js/src/ui/ShortenedHash/shortenedHash.js b/js/src/ui/ShortenedHash/shortenedHash.js
new file mode 100644
index 000000000..e3d5c3b14
--- /dev/null
+++ b/js/src/ui/ShortenedHash/shortenedHash.js
@@ -0,0 +1,41 @@
+// Copyright 2015, 2016 Ethcore (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 React, { Component, PropTypes } from 'react';
+
+import styles from './shortenedHash.css';
+
+export default class ShortenedHash extends Component {
+ static propTypes = {
+ data: PropTypes.string.isRequired
+ }
+
+ render () {
+ const { data } = this.props;
+
+ let shortened = data.toLowerCase();
+ if (shortened.slice(0, 2) === '0x') {
+ shortened = shortened.slice(2);
+ }
+ if (shortened.length > (6 + 6)) {
+ shortened = shortened.slice(0, 6) + '…' + shortened.slice(-6);
+ }
+
+ return (
+ { shortened }
+ );
+ }
+}
diff --git a/js/src/ui/TxHash/txHash.css b/js/src/ui/TxHash/txHash.css
index ef55ee701..c5b54e351 100644
--- a/js/src/ui/TxHash/txHash.css
+++ b/js/src/ui/TxHash/txHash.css
@@ -15,19 +15,12 @@
/* along with Parity. If not, see .
*/
-.details {
-}
-
-.header {
-}
-
.hash {
padding-top: 1em;
word-break: break-all;
}
.confirm {
- padding-top: 1em;
opacity: 0.5;
}
diff --git a/js/src/ui/TxHash/txHash.js b/js/src/ui/TxHash/txHash.js
index 83e5c79f5..81c4e0bf5 100644
--- a/js/src/ui/TxHash/txHash.js
+++ b/js/src/ui/TxHash/txHash.js
@@ -19,7 +19,9 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { LinearProgress } from 'material-ui';
+
import { txLink } from '../../3rdparty/etherscan/links';
+import ShortenedHash from '../ShortenedHash';
import styles from './txHash.css';
@@ -62,22 +64,23 @@ class TxHash extends Component {
render () {
const { hash, isTest, summary } = this.props;
- let header = null;
- if (!summary) {
- header = (
-
- The transaction has been posted to the network with a transaction hash of
-
- );
+ const link = (
+
+
+
+ );
+
+ let header = (
+
The transaction has been posted to the network, with a hash of { link }.
);
diff --git a/js/src/ui/index.js b/js/src/ui/index.js
index d443d0dbc..6824d9887 100644
--- a/js/src/ui/index.js
+++ b/js/src/ui/index.js
@@ -37,6 +37,7 @@ import Modal, { Busy as BusyStep, Completed as CompletedStep } from './Modal';
import muiTheme from './Theme';
import Page from './Page';
import ParityBackground from './ParityBackground';
+import ShortenedHash from './ShortenedHash';
import SignerIcon from './SignerIcon';
import Tags from './Tags';
import Tooltips, { Tooltip } from './Tooltips';
@@ -79,6 +80,7 @@ export {
Page,
ParityBackground,
RadioButtons,
+ ShortenedHash,
SignerIcon,
Tags,
Tooltip,
diff --git a/js/src/util/is-testnet.js b/js/src/util/is-testnet.js
new file mode 100644
index 000000000..c2bf2c450
--- /dev/null
+++ b/js/src/util/is-testnet.js
@@ -0,0 +1,19 @@
+// Copyright 2015, 2016 Ethcore (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 default (chain) => {
+ return chain === 'morden' || chain === 'ropsten' || chain === 'testnet';
+};
diff --git a/js/src/util/subscribe-to-event.js b/js/src/util/subscribe-to-event.js
new file mode 100644
index 000000000..3313404c5
--- /dev/null
+++ b/js/src/util/subscribe-to-event.js
@@ -0,0 +1,77 @@
+// Copyright 2015, 2016 Ethcore (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/views/Account/Transactions/Transaction/transaction.js b/js/src/views/Account/Transactions/Transaction/transaction.js
index d84917d6b..32aa406a8 100644
--- a/js/src/views/Account/Transactions/Transaction/transaction.js
+++ b/js/src/views/Account/Transactions/Transaction/transaction.js
@@ -19,6 +19,7 @@ import React, { Component, PropTypes } from 'react';
import moment from 'moment';
import { IdentityIcon, IdentityName, MethodDecoding } from '../../../../ui';
+import ShortenedHash from '../../../../ui/ShortenedHash';
import { txLink, addressLink } from '../../../../3rdparty/etherscan/links';
import styles from '../transactions.css';
@@ -95,7 +96,7 @@ export default class Transaction extends Component {
href={ txLink(transaction.hash, isTest) }
target='_blank'
>
- { this.formatHash(transaction.hash) }
+