diff --git a/js/src/api/format/input.js b/js/src/api/format/input.js index 74ddd4e56ba..34871c9111a 100644 --- a/js/src/api/format/input.js +++ b/js/src/api/format/input.js @@ -143,8 +143,15 @@ export function inOptions (options) { if (options) { Object.keys(options).forEach((key) => { switch (key) { - case 'from': case 'to': + // Don't encode the `to` option if it's empty + // (eg. contract deployments) + if (options[key]) { + options[key] = inAddress(options[key]); + } + break; + + case 'from': options[key] = inAddress(options[key]); break; diff --git a/js/src/api/format/input.spec.js b/js/src/api/format/input.spec.js index 450ff61cc66..4b82bd1ef5a 100644 --- a/js/src/api/format/input.spec.js +++ b/js/src/api/format/input.spec.js @@ -208,6 +208,13 @@ describe('api/format/input', () => { }); }); + it('does not encode an empty `to` value', () => { + const options = { to: '' }; + const formatted = inOptions(options); + + expect(formatted.to).to.equal(''); + }); + ['gas', 'gasPrice', 'value', 'minBlock', 'nonce'].forEach((input) => { it(`formats ${input} number as hexnumber`, () => { const block = {}; diff --git a/js/src/contracts/registry.js b/js/src/contracts/registry.js index 80f54ea03df..cef31785e06 100644 --- a/js/src/contracts/registry.js +++ b/js/src/contracts/registry.js @@ -16,7 +16,14 @@ import * as abis from './abi'; +const REGISTRY_V1_HASHES = [ + '0x34f7c51bbb1b1902fbdabfdf04811100f5c9f998f26dd535d2f6f977492c748e', // ropsten + '0x64c3ee34851517a9faecd995c102b339f03e564ad6772dc43a26f993238b20ec' // homestead +]; + export default class Registry { + _registryContract = null; + constructor (api) { this._api = api; @@ -43,11 +50,10 @@ export default class Registry { this._fetching = true; - return this._api.parity - .registryAddress() - .then((address) => { + return this.fetchContract() + .then((contract) => { this._fetching = false; - this._instance = this._api.newContract(abis.registry, address).instance; + this._instance = contract.instance; this._queue.forEach((queued) => { queued.resolve(this._instance); @@ -89,6 +95,47 @@ export default class Registry { .then((contract) => contract.instance); } + fetchContract () { + if (this._registryContract) { + return Promise.resolve(this._registryContract); + } + + return this._api.parity + .registryAddress() + .then((address) => Promise.all([ address, this._api.eth.getCode(address) ])) + .then(([ address, code ]) => { + const codeHash = this._api.util.sha3(code); + const version = REGISTRY_V1_HASHES.includes(codeHash) + ? 1 + : 2; + const abi = version === 1 + ? abis.registry + : abis.registry2; + const contract = this._api.newContract(abi, address); + + // Add support for previous `set` and `get` methods + if (!contract.instance.get && contract.instance.getData) { + contract.instance.get = contract.instance.getData; + } + + if (contract.instance.get && !contract.instance.getData) { + contract.instance.getData = contract.instance.get; + } + + if (!contract.instance.set && contract.instance.setData) { + contract.instance.set = contract.instance.setData; + } + + if (contract.instance.set && !contract.instance.setData) { + contract.instance.setData = contract.instance.set; + } + + console.log(`registry at ${address}, code ${codeHash}, version ${version}`); + this._registryContract = contract; + return this._registryContract; + }); + } + _createGetParams (_name, key) { const name = _name.toLowerCase(); const sha3 = this._api.util.sha3.text(name); diff --git a/js/src/contracts/registry.spec.js b/js/src/contracts/registry.spec.js index 7cec52e9426..fa7157ef121 100644 --- a/js/src/contracts/registry.spec.js +++ b/js/src/contracts/registry.spec.js @@ -35,6 +35,9 @@ function create () { } }; api = { + eth: { + getCode: sinon.stub().resolves(0) + }, parity: { registryAddress: sinon.stub().resolves('testRegistryAddress') }, diff --git a/js/src/dapps/githubhint.js b/js/src/dapps/githubhint.js index 14db7c7b3d0..1e55b91f517 100644 --- a/js/src/dapps/githubhint.js +++ b/js/src/dapps/githubhint.js @@ -16,6 +16,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; +import { AppContainer } from 'react-hot-loader'; import injectTapEventPlugin from 'react-tap-event-plugin'; injectTapEventPlugin(); @@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css'; import './style.css'; ReactDOM.render( - , + + + , document.querySelector('#container') ); + +if (module.hot) { + module.hot.accept('./githubhint/Application/index.js', () => { + require('./githubhint/Application/index.js'); + + ReactDOM.render( + + + , + document.querySelector('#container') + ); + }); +} diff --git a/js/src/dapps/localtx.js b/js/src/dapps/localtx.js index 8d8db8caf95..4625ded208c 100644 --- a/js/src/dapps/localtx.js +++ b/js/src/dapps/localtx.js @@ -16,6 +16,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; +import { AppContainer } from 'react-hot-loader'; import injectTapEventPlugin from 'react-tap-event-plugin'; injectTapEventPlugin(); @@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css'; import './style.css'; ReactDOM.render( - , + + + , document.querySelector('#container') ); + +if (module.hot) { + module.hot.accept('./localtx/Application/index.js', () => { + require('./localtx/Application/index.js'); + + ReactDOM.render( + + + , + document.querySelector('#container') + ); + }); +} diff --git a/js/src/dapps/localtx/Application/application.css b/js/src/dapps/localtx/Application/application.css index 4b5f0bc314c..15762c5d477 100644 --- a/js/src/dapps/localtx/Application/application.css +++ b/js/src/dapps/localtx/Application/application.css @@ -15,5 +15,29 @@ th { text-align: center; } + + td { + text-align: center; + } + } + + button { + background-color: rgba(0, 136, 170, 1); + border: none; + border-radius: 5px; + color: white; + font-size: 1rem; + padding: 0.5em 1em; + width: 100%; + + &:hover { + background-color: rgba(0, 136, 170, 0.8); + cursor: pointer; + } + } + + input { + font-size: 1rem; + padding: 0.5em 1em; } } diff --git a/js/src/dapps/localtx/Application/application.js b/js/src/dapps/localtx/Application/application.js index a36eb84f4b7..8efadcf1a2e 100644 --- a/js/src/dapps/localtx/Application/application.js +++ b/js/src/dapps/localtx/Application/application.js @@ -70,7 +70,6 @@ export default class Application extends Component { local[tx.hash].transaction = tx; local[tx.hash].stats = data.stats; }); - // Convert local transactions to array const localTransactions = Object.keys(local).map(hash => { const data = local[hash]; diff --git a/js/src/dapps/localtx/Transaction/transaction.css b/js/src/dapps/localtx/Transaction/transaction.css index c49d9479fd4..b06942ed768 100644 --- a/js/src/dapps/localtx/Transaction/transaction.css +++ b/js/src/dapps/localtx/Transaction/transaction.css @@ -6,6 +6,14 @@ } } +.txhash { + display: inline-block; + overflow: hidden; + padding-right: 3ch; + text-overflow: ellipsis; + width: 10ch; +} + .transaction { td { padding: 7px 15px; diff --git a/js/src/dapps/localtx/Transaction/transaction.js b/js/src/dapps/localtx/Transaction/transaction.js index 56a69785345..554c2b757cc 100644 --- a/js/src/dapps/localtx/Transaction/transaction.js +++ b/js/src/dapps/localtx/Transaction/transaction.js @@ -31,8 +31,8 @@ class BaseTransaction extends Component { renderHash (hash) { return ( - - { this.shortHash(hash) } + + { hash } ); } @@ -206,7 +206,10 @@ export class LocalTransaction extends BaseTransaction { From - Gas Price / Gas + Gas Price + + + Gas Status @@ -224,18 +227,18 @@ export class LocalTransaction extends BaseTransaction { toggleResubmit = () => { const { transaction } = this.props; - const { isResubmitting, gasPrice } = this.state; + const { isResubmitting } = this.state; - this.setState({ + const nextState = { isResubmitting: !isResubmitting - }); + }; - if (gasPrice === null) { - this.setState({ - gasPrice: `0x${transaction.gasPrice.toString(16)}`, - gas: `0x${transaction.gas.toString(16)}` - }); + if (!isResubmitting) { + nextState.gasPrice = api.util.fromWei(transaction.gasPrice, 'shannon').toNumber(); + nextState.gas = transaction.gas.div(1000000).toNumber(); } + + this.setState(nextState); }; setGasPrice = el => { @@ -251,16 +254,15 @@ export class LocalTransaction extends BaseTransaction { }; sendTransaction = () => { - const { transaction } = this.props; + const { transaction, status } = this.props; const { gasPrice, gas } = this.state; const newTransaction = { from: transaction.from, - to: transaction.to, - nonce: transaction.nonce, value: transaction.value, data: transaction.input, - gasPrice, gas + gasPrice: api.util.toWei(gasPrice, 'shannon'), + gas: new BigNumber(gas).mul(1000000) }; this.setState({ @@ -268,11 +270,21 @@ export class LocalTransaction extends BaseTransaction { isSending: true }); - const closeSending = () => this.setState({ - isSending: false, - gasPrice: null, - gas: null - }); + const closeSending = () => { + this.setState({ + isSending: false, + gasPrice: null, + gas: null + }); + }; + + if (transaction.to) { + newTransaction.to = transaction.to; + } + + if (!['mined', 'replaced'].includes(status)) { + newTransaction.nonce = transaction.nonce; + } api.eth.sendTransaction(newTransaction) .then(closeSending) @@ -290,9 +302,9 @@ export class LocalTransaction extends BaseTransaction { const resubmit = isSending ? ( 'sending...' ) : ( - + ); return ( @@ -308,7 +320,8 @@ export class LocalTransaction extends BaseTransaction { { this.renderGasPrice(transaction) } -
+ + { this.renderGas(transaction) } @@ -345,9 +358,9 @@ export class LocalTransaction extends BaseTransaction { return ( - + { this.renderHash(transaction.hash) } @@ -357,20 +370,24 @@ export class LocalTransaction extends BaseTransaction { + shannon + + + MGas - + ); diff --git a/js/src/dapps/signaturereg.js b/js/src/dapps/signaturereg.js index c20e45170ff..61b67aab027 100644 --- a/js/src/dapps/signaturereg.js +++ b/js/src/dapps/signaturereg.js @@ -16,6 +16,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; +import { AppContainer } from 'react-hot-loader'; import injectTapEventPlugin from 'react-tap-event-plugin'; injectTapEventPlugin(); @@ -27,6 +28,21 @@ import '../../assets/fonts/RobotoMono/font.css'; import './style.css'; ReactDOM.render( - , + + + , document.querySelector('#container') ); + +if (module.hot) { + module.hot.accept('./signaturereg/Application/index.js', () => { + require('./signaturereg/Application/index.js'); + + ReactDOM.render( + + + , + document.querySelector('#container') + ); + }); +} diff --git a/js/src/dapps/tokendeploy.js b/js/src/dapps/tokendeploy.js index a1808199e04..b6de05d0581 100644 --- a/js/src/dapps/tokendeploy.js +++ b/js/src/dapps/tokendeploy.js @@ -17,6 +17,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { Redirect, Router, Route, hashHistory } from 'react-router'; +import { AppContainer } from 'react-hot-loader'; import injectTapEventPlugin from 'react-tap-event-plugin'; injectTapEventPlugin(); @@ -31,13 +32,37 @@ import '../../assets/fonts/RobotoMono/font.css'; import './style.css'; ReactDOM.render( - - - - - - - - , + + + + + + + + + + , document.querySelector('#container') ); + +if (module.hot) { + module.hot.accept('./tokendeploy/Application/index.js', () => { + require('./tokendeploy/Application/index.js'); + require('./tokendeploy/Overview/index.js'); + require('./tokendeploy/Transfer/index.js'); + + ReactDOM.render( + + + + + + + + + + , + document.querySelector('#container') + ); + }); +} diff --git a/js/src/dapps/tokendeploy/services.js b/js/src/dapps/tokendeploy/services.js index 9ca4c4f5650..6cfeff05f76 100644 --- a/js/src/dapps/tokendeploy/services.js +++ b/js/src/dapps/tokendeploy/services.js @@ -119,7 +119,7 @@ export function attachInstances () { .all([ api.parity.registryAddress(), api.parity.netChain(), - api.partiy.netVersion() + api.net.version() ]) .then(([registryAddress, netChain, _netVersion]) => { const registry = api.newContract(abis.registry, registryAddress).instance; diff --git a/js/src/dapps/tokenreg.js b/js/src/dapps/tokenreg.js index 5c6bb4bd1ec..3e8dc9b1942 100644 --- a/js/src/dapps/tokenreg.js +++ b/js/src/dapps/tokenreg.js @@ -17,6 +17,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { Provider } from 'react-redux'; +import { AppContainer } from 'react-hot-loader'; import injectTapEventPlugin from 'react-tap-event-plugin'; injectTapEventPlugin(); @@ -29,10 +30,25 @@ import '../../assets/fonts/RobotoMono/font.css'; import './style.css'; ReactDOM.render( - ( + - ), + , document.querySelector('#container') ); + +if (module.hot) { + module.hot.accept('./tokenreg/Container.js', () => { + require('./tokenreg/Container.js'); + + ReactDOM.render( + + + + + , + document.querySelector('#container') + ); + }); +} diff --git a/js/src/modals/DeployContract/deployContract.js b/js/src/modals/DeployContract/deployContract.js index 14930d312ba..802de64fb50 100644 --- a/js/src/modals/DeployContract/deployContract.js +++ b/js/src/modals/DeployContract/deployContract.js @@ -142,12 +142,13 @@ class DeployContract extends Component { render () { const { step, deployError, rejected, inputs } = this.state; - const realStep = Object.keys(STEPS).findIndex((k) => k === step); - const realSteps = deployError || rejected - ? null - : Object.keys(STEPS) - .filter((k) => k !== 'CONTRACT_PARAMETERS' || inputs.length > 0) - .map((k) => STEPS[k]); + const realStepKeys = deployError || rejected + ? [] + : Object.keys(STEPS).filter((k) => k !== 'CONTRACT_PARAMETERS' || inputs.length > 0); + const realStep = realStepKeys.findIndex((k) => k === step); + const realSteps = realStepKeys.length + ? realStepKeys.map((k) => STEPS[k]) + : null; const title = realSteps ? null diff --git a/js/src/ui/Form/Input/input.js b/js/src/ui/Form/Input/input.js index 44906ad93ff..64ec544b9b5 100644 --- a/js/src/ui/Form/Input/input.js +++ b/js/src/ui/Form/Input/input.js @@ -214,6 +214,7 @@ export default class Input extends Component { onChange = (event, value) => { event.persist(); + this.setValue(value, () => { this.props.onChange && this.props.onChange(event, value); }); @@ -231,12 +232,10 @@ export default class Input extends Component { } onPaste = (event) => { - const { value } = event.target; - const pasted = event.clipboardData.getData('Text'); - + // Wait for the onChange handler to be called window.setTimeout(() => { - this.onSubmit(value + pasted); - }, 0); + this.onSubmit(this.state.value); + }, 200); } onKeyDown = (event) => { diff --git a/js/src/ui/MethodDecoding/methodDecodingStore.js b/js/src/ui/MethodDecoding/methodDecodingStore.js index 05a8f8546a3..1b8671465cf 100644 --- a/js/src/ui/MethodDecoding/methodDecodingStore.js +++ b/js/src/ui/MethodDecoding/methodDecodingStore.js @@ -138,6 +138,10 @@ export default class MethodDecodingStore { return Promise.resolve(result); } + if (!transaction.to) { + return this.decodeContractCreation(result); + } + let signature; try { @@ -206,7 +210,7 @@ export default class MethodDecodingStore { }); } - decodeContractCreation (data, contractAddress) { + decodeContractCreation (data, contractAddress = '') { const result = { ...data, contract: true,