diff --git a/Cargo.lock b/Cargo.lock index 62ef5ea11..44b437505 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1263,7 +1263,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#7cb42b0c636f76eb478c9270a1e507ac3c3ba434" +source = "git+https://github.com/ethcore/js-precompiled.git#0ad3b5d2b885f9b3d582bedebec2f8db0238b4b9" dependencies = [ "parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/js/package.json b/js/package.json index 534e1d387..49006e916 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "0.2.74", + "version": "0.2.76", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -131,6 +131,7 @@ "es6-error": "~4.0.0", "es6-promise": "~3.2.1", "ethereumjs-tx": "~1.1.2", + "eventemitter3": "~2.0.2", "file-saver": "~1.3.3", "format-json": "~1.0.3", "format-number": "~2.0.1", diff --git a/js/src/3rdparty/sms-verification/index.js b/js/src/3rdparty/sms-verification/index.js index 9b113f364..c50b2331a 100644 --- a/js/src/3rdparty/sms-verification/index.js +++ b/js/src/3rdparty/sms-verification/index.js @@ -27,9 +27,10 @@ export const termsOfService = ( ); -export const postToServer = (query) => { +export const postToServer = (query, isTestnet = false) => { + const port = isTestnet ? 8443 : 443; query = stringify(query); - return fetch('https://sms-verification.parity.io/?' + query, { + return fetch(`https://sms-verification.parity.io:${port}/?` + query, { method: 'POST', mode: 'cors', cache: 'no-store' }) .then((res) => { diff --git a/js/src/contracts/abi/sms-verification.json b/js/src/contracts/abi/sms-verification.json index 400d22b44..d6852b182 100644 --- a/js/src/contracts/abi/sms-verification.json +++ b/js/src/contracts/abi/sms-verification.json @@ -1 +1 @@ -[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] +[{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"certify","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"request","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"},{"name":"_puzzle","type":"bytes32"}],"name":"puzzle","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"uint256"}],"name":"setFee","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_who","type":"address"}],"name":"revoke","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_code","type":"bytes32"}],"name":"confirm","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"drain","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"delegate","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"getUint","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_new","type":"address"}],"name":"setDelegate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"}],"name":"certified","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"fee","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_who","type":"address"},{"name":"_field","type":"string"}],"name":"get","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Requested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"},{"indexed":false,"name":"puzzle","type":"bytes32"}],"name":"Puzzled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Confirmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"who","type":"address"}],"name":"Revoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"old","type":"address"},{"indexed":true,"name":"current","type":"address"}],"name":"NewOwner","type":"event"}] diff --git a/js/src/contracts/sms-verification.js b/js/src/contracts/sms-verification.js index 2d32556ea..34a6bad76 100644 --- a/js/src/contracts/sms-verification.js +++ b/js/src/contracts/sms-verification.js @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +import subscribeToEvent from '../util/subscribe-to-event'; + export const checkIfVerified = (contract, account) => { return contract.instance.certified.call({}, [account]); }; @@ -50,3 +52,36 @@ export const checkIfRequested = (contract, account) => { }); }); }; + +const blockNumber = (api) => { + return new Promise((resolve, reject) => { + api.subscribe('eth_blockNumber', (err, block) => { + if (err) { + return reject(err); + } + resolve(block); + }) + .then((subscription) => { + api.unsubscribe(subscription); + }) + .catch(reject); + }); +}; + +export const awaitPuzzle = (api, contract, account) => { + return blockNumber(api) + .then((block) => { + return new Promise((resolve, reject) => { + const subscription = subscribeToEvent(contract, 'Puzzled', { + from: block.toNumber(), + filter: (log) => log.params.who.value === account + }); + subscription.once('error', reject); + subscription.once('log', subscription.unsubscribe); + subscription.once('log', resolve); + subscription.once('timeout', () => { + reject(new Error('Timed out waiting for the puzzle.')); + }); + }); + }); +}; diff --git a/js/src/modals/SMSVerification/GatherData/gatherData.js b/js/src/modals/SMSVerification/GatherData/gatherData.js index 3620de904..3d90fa2ba 100644 --- a/js/src/modals/SMSVerification/GatherData/gatherData.js +++ b/js/src/modals/SMSVerification/GatherData/gatherData.js @@ -53,7 +53,7 @@ export default class GatherData extends Component { { this.renderCertified() } { this.renderRequested() } Requesting an SMS from the Parity server.

+

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 }.

+ ); + if (summary) { + header = (

{ link }

); } return ( -
+
{ header } -
- { hash } -
{ this.renderConfirmations() }
); @@ -87,17 +90,29 @@ class TxHash extends Component { const { maxConfirmations } = this.props; const { blockNumber, transaction } = this.state; - let txBlock = 'Pending'; - let confirmations = 'No'; - let value = 0; - - if (transaction && transaction.blockNumber && transaction.blockNumber.gt(0)) { - const num = blockNumber.minus(transaction.blockNumber).plus(1); - txBlock = `#${transaction.blockNumber.toFormat(0)}`; - confirmations = num.toFormat(0); - value = num.gt(maxConfirmations) ? maxConfirmations : num.toNumber(); + if (!(transaction && transaction.blockNumber && transaction.blockNumber.gt(0))) { + return ( +
+ +
waiting for confirmations
+
+ ); } + const confirmations = blockNumber.minus(transaction.blockNumber).plus(1); + const value = Math.min(confirmations.toNumber(), maxConfirmations); + let count; + if (confirmations.gt(maxConfirmations)) { + count = confirmations.toFormat(0); + } else { + count = confirmations.toFormat(0) + `/${maxConfirmations}`; + } + const unit = value === 1 ? 'confirmation' : 'confirmations'; + return (
+ mode='determinate' + />
- { txBlock } / { confirmations } confirmations + { count } { unit }
); 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) } +
@@ -150,14 +151,6 @@ export default class Transaction extends Component { ); } - formatHash (hash) { - if (!hash || hash.length <= 16) { - return hash; - } - - return `${hash.substr(2, 6)}...${hash.slice(-6)}`; - } - formatNumber (number) { return new BigNumber(number).toFormat(); } diff --git a/js/src/views/Account/account.js b/js/src/views/Account/account.js index 1181b7f73..e27333cbf 100644 --- a/js/src/views/Account/account.js +++ b/js/src/views/Account/account.js @@ -47,6 +47,7 @@ class Account extends Component { params: PropTypes.object, accounts: PropTypes.object, + isTestnet: PropTypes.bool, balances: PropTypes.object } @@ -65,8 +66,9 @@ class Account extends Component { componentDidMount () { const { api } = this.context; const { address } = this.props.params; + const { isTestnet } = this.props; - const verificationStore = new VerificationStore(api, address); + const verificationStore = new VerificationStore(api, address, isTestnet); this.setState({ verificationStore }); this.setVisibleAccounts(); } @@ -326,11 +328,13 @@ class Account extends Component { function mapStateToProps (state) { const { accounts } = state.personal; + const { isTest } = state.nodeStatus; const { balances } = state.balances; const { images } = state; return { accounts, + isTestnet: isTest, balances, images }; diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index 53b5b4778..c8fa0fd4a 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -19,6 +19,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { Tab as MUITab } from 'material-ui/Tabs'; +import { isEqual } from 'lodash'; import { Badge, Tooltip } from '../../../ui'; @@ -162,9 +163,13 @@ class TabBar extends Component { } shouldComponentUpdate (nextProps, nextState) { + const prevViews = this.props.views.map((v) => v.id).sort(); + const nextViews = nextProps.views.map((v) => v.id).sort(); + return (nextProps.hash !== this.props.hash) || (nextProps.pending.length !== this.props.pending.length) || - (nextState.activeViewId !== this.state.activeViewId); + (nextState.activeViewId !== this.state.activeViewId) || + (!isEqual(prevViews, nextViews)); } render () { @@ -217,7 +222,7 @@ class TabBar extends Component { active={ active } view={ view } onChange={ this.onChange } - key={ index } + key={ view.id } pendings={ pending.length } > { body } diff --git a/js/src/views/Contract/Events/Event/event.js b/js/src/views/Contract/Events/Event/event.js index f45975f94..17fd56051 100644 --- a/js/src/views/Contract/Events/Event/event.js +++ b/js/src/views/Contract/Events/Event/event.js @@ -19,6 +19,7 @@ import moment from 'moment'; import React, { Component, PropTypes } from 'react'; import { IdentityIcon, IdentityName, Input, InputAddress } from '../../../../ui'; +import ShortenedHash from '../../../../ui/ShortenedHash'; import { txLink } from '../../../../3rdparty/etherscan/links'; import styles from '../../contract.css'; @@ -71,7 +72,7 @@ export default class Event extends Component {
{ event.type }({ keys })
- { this.formatHash(event.transactionHash) } +
@@ -82,14 +83,6 @@ export default class Event extends Component { ); } - formatHash (hash) { - if (!hash || hash.length <= 16) { - return hash; - } - - return `${hash.substr(2, 6)}...${hash.slice(-6)}`; - } - renderAddressName (address, withName = true) { return ( diff --git a/js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.css b/js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.css index 6612d960e..107694b8e 100644 --- a/js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.css +++ b/js/src/views/Signer/components/TransactionMainDetails/TransactionMainDetails.css @@ -19,6 +19,7 @@ .transaction { flex: 1; + overflow: auto; } .transaction > * {