From 7d88196634e56d2c7dd47c35ec95da376e46734f Mon Sep 17 00:00:00 2001 From: Silas Albrecht Date: Sun, 11 Mar 2018 11:20:03 -0500 Subject: [PATCH 01/11] U --- README.md | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 3994199..3fa312f 100644 --- a/README.md +++ b/README.md @@ -97,31 +97,13 @@ document.getElementById("runInvokeButton).addEventListener("click", ``` -Please note that currently the code is limited to a maximum of three arguments to the smart contract. +Please note that currently the code is limited to a maximum of two arguments to the smart contract. - -## Triggering an IoT Smart Contract Payment - -### Python - -Python command line: (must be synced to TestNet) -``` -testinvoke b3a14d99a3fb6646c78bf2f4e2f25a7964d2956a putvalue ['test','0000ff'] --attach-gas=0.000025 - -``` - -### NeoLink +## NeoLink - Demo - Install NeoLink - Clone github.com/cityofzion/neolink/ - Follow the instructions there to install and build - Login with encrypted WIF (wallet needs a balance of TestNet gas) -- Login to iot.splyse.tech - - user: neo@splyse.tech - - pass: neo -- Locate contract with name 'test' in the list of Devices -- Enter a color code hex value in the form of '00ff00' (without quotes) into the input field -- Click pay -- If you are logged into your NeoLink wallet you will see a message in the web page asking you to open NeoLink and authorize the transaction. -- Open NeoLink and click the 'yes' button to authorize the transaction. - +- Visit sendeo.surge.sh and watch the video to learn how it works. +- Code available at [on Githhub](https://github.com/slipo/sendeo) From de88598525e60669a9f475775272577b2340043a Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Sun, 15 Apr 2018 16:37:44 +0200 Subject: [PATCH 02/11] Rewrite create account with wif --- __tests__/containers/CreateWallet.test.js | 27 +++- src/app/components/HoC/withCreateAccount.js | 64 ++++++++ src/app/containers/App/App.js | 3 +- .../containers/CreateWallet/CreateWallet.js | 26 ++- src/app/containers/CreateWallet/index.js | 6 +- .../CreateWalletWithWif.css | 19 +++ .../CreateWalletWithWif.js | 149 ++++++++++++++++++ .../containers/CreateWalletWithWif/index.js | 21 +++ 8 files changed, 302 insertions(+), 13 deletions(-) create mode 100644 src/app/components/HoC/withCreateAccount.js create mode 100644 src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css create mode 100644 src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js create mode 100644 src/app/containers/CreateWalletWithWif/index.js diff --git a/__tests__/containers/CreateWallet.test.js b/__tests__/containers/CreateWallet.test.js index 7b995e0..8443a09 100644 --- a/__tests__/containers/CreateWallet.test.js +++ b/__tests__/containers/CreateWallet.test.js @@ -8,9 +8,18 @@ import Loader from '../../src/app/components/Loader' jest.useFakeTimers() +const accounts = { + ARjkxk6VcKPFKqRHhuLNog9TbdYxhKu9be: { + address: 'ARjkxk6VcKPFKqRHhuLNog9TbdYxhKu9be', + isDefault: false, + key: '6PYRop1b45uKRUVUngUr3g44UmH8Kg6KTVTAvxyKKJLVpxQsM5HXUPrzCB', + label: 'TestKonto', + }, +} + describe('CreateWallet', () => { test('shows loading', () => { - const wrapper = shallow() + const wrapper = shallow() wrapper.setState({ loading: true }) expect(wrapper.find(Loader).length).toEqual(1) }) @@ -21,7 +30,9 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() const addAccount = jest.fn() - const wrapper = mount() + const wrapper = mount( + + ) wrapper .find('input#passPhraseConfirm') @@ -50,7 +61,7 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() - const wrapper = mount() + const wrapper = mount() wrapper .find('input#passPhraseConfirm') @@ -70,7 +81,7 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() - const wrapper = mount() + const wrapper = mount() wrapper .find('input#passPhraseConfirm') @@ -91,7 +102,9 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() const addAccount = jest.fn() - const wrapper = mount() + const wrapper = mount( + + ) wrapper .find('input#wif') @@ -126,7 +139,9 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() - const wrapper = mount() + const wrapper = mount( + + ) wrapper .find('input#wif') diff --git a/src/app/components/HoC/withCreateAccount.js b/src/app/components/HoC/withCreateAccount.js new file mode 100644 index 0000000..d51c90e --- /dev/null +++ b/src/app/components/HoC/withCreateAccount.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react' +import { validateLength } from '../../utils/helpers' + +export function withCreateAccount(WrappedComponent, accounts) { + return class extends Component { + _clearErrors(key) { + this._setErrorState(key, '') + } + + _setErrorState = (key, value) => { + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [key]: value, + }, + })) + } + + _handleTextFieldChange = e => { + const key = e.target.id + + this._clearErrors(key) + this.setState({ + [key]: e.target.value, + }) + } + + _labelExists = label => { + const labelExists = Object.keys(accounts) + .map(account => { + return accounts[account.label] + }) + .find(accountLabel => accountLabel === label) + + return !!labelExists + } + + _validateLabel = () => { + const { label } = this.state + const labelExists = this._labelExists(label) + + if (!validateLength(label, 1)) { + this._setErrorState('label', 'Account name must be longer than 1.') + return false + } else if (!labelExists) { + this._setErrorState('label', 'You already have an account with that label') + return false + } else { + this._setErrorState('label', '') + return true + } + } + + render() { + return ( + + ) + } + } +} diff --git a/src/app/containers/App/App.js b/src/app/containers/App/App.js index 8ecc80a..75c247e 100644 --- a/src/app/containers/App/App.js +++ b/src/app/containers/App/App.js @@ -9,6 +9,7 @@ import SendInvoke from '../SendInvoke' import Transactions from '../Transactions' import Balance from '../Balance' import CreateWallet from '../CreateWallet' +import CreateWalletWithWif from '../CreateWalletWithWif' import ImportWallet from '../ImportWallet' import ExportWallet from '../ExportWallet' import Config from '../Config' @@ -38,7 +39,7 @@ export default class App extends Component { - } /> + diff --git a/src/app/containers/CreateWallet/CreateWallet.js b/src/app/containers/CreateWallet/CreateWallet.js index cbfb7d0..45f2eaf 100644 --- a/src/app/containers/CreateWallet/CreateWallet.js +++ b/src/app/containers/CreateWallet/CreateWallet.js @@ -51,12 +51,27 @@ export default class CreateWallet extends Component { }) } + _labelExists = label => { + const { accounts } = this.props + const labelExists = Object.keys(accounts) + .map(account => { + return accounts[account].label + }) + .find(accountLabel => accountLabel === label) + + return !!labelExists + } + _validateLabel = () => { const { label } = this.state + const labelExists = this._labelExists(label) if (!validateLength(label, 1)) { this._setErrorState('label', 'Account name must be longer than 1.') return false + } else if (labelExists) { + this._setErrorState('label', 'You already have an account with that label') + return false } else { this._setErrorState('label', '') return true @@ -119,9 +134,9 @@ export default class CreateWallet extends Component { const { addAccount, manualWIF, setAccount } = this.props const validated = this._validate() - if (validated) { - // Make wallet.decrypt() async. + this.setState({ loading: true }) + const account = new wallet.Account(manualWIF ? wif : wallet.generatePrivateKey()) wallet @@ -142,9 +157,10 @@ export default class CreateWallet extends Component { encryptedWif: encryptedWif, address: account.address, }, - () => setAccount(account.WIF, account.address) + () => setAccount(encryptedWif, account.address) ) - }).catch(e => { + }) + .catch(e => { this.setState({ loading: false, errorMsg: e.message }) }) } @@ -157,7 +173,6 @@ export default class CreateWallet extends Component { if (loading) { return } else if (encryptedWif) { - // handle success return } @@ -214,4 +229,5 @@ CreateWallet.propTypes = { setAccount: PropTypes.func.isRequired, manualWIF: PropTypes.bool, history: PropTypes.object.isRequired, + accounts: PropTypes.object.isRequired, } diff --git a/src/app/containers/CreateWallet/index.js b/src/app/containers/CreateWallet/index.js index 40c1cff..833a6b4 100644 --- a/src/app/containers/CreateWallet/index.js +++ b/src/app/containers/CreateWallet/index.js @@ -12,6 +12,10 @@ const actionCreators = { setAccount, } +const mapStateToProps = state => ({ + accounts: state.wallet.accounts, +}) + const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) -export default withRouter(connect(null, mapDispatchToProps)(CreateWallet)) +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(CreateWallet)) diff --git a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css new file mode 100644 index 0000000..a4000e4 --- /dev/null +++ b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css @@ -0,0 +1,19 @@ +.createWalletWithWifWrapper { + background-color: #f1f1f1; + padding: 20px; + height: 100%; +} + +.createWalletWithWifHeading { + margin-bottom: 30px; + width: 80%; + text-align: center; +} + +.createWalletWithWifButton { + margin: 15px auto 0 auto; +} + +.createWalletWithWifForm { + width: 100%; +} diff --git a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js new file mode 100644 index 0000000..10def44 --- /dev/null +++ b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js @@ -0,0 +1,149 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +import { wallet } from '@cityofzion/neon-js' +import { validateLength } from '../../utils/helpers' + +import Box from '../../components/common/Box' +import InputField from '../../components/common/form/InputField' +import PrimaryButton from '../../components/common/buttons/PrimaryButton' + +import style from './CreateWalletWithWif.css' + +class CreateWalletWithWif extends Component { + state = { + encryptedWif: '', + passPhrase: '', + label: '', + errors: { + wif: '', + passPhrase: '', + label: '', + }, + } + + _clearErrors(key) { + this._setErrorState(key, '') + } + + _setErrorState = (key, value) => { + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [key]: value, + }, + })) + } + + _labelExists = label => { + const { accounts } = this.props + const labelExists = Object.keys(accounts) + .map(account => { + return accounts[account].label + }) + .find(accountLabel => accountLabel === label) + + return !!labelExists + } + + _validateLabel = () => { + const { label } = this.state + const labelExists = this._labelExists(label) + + if (!validateLength(label, 1)) { + this._setErrorState('label', 'Account name must be longer than 1.') + return false + } else if (labelExists) { + this._setErrorState('label', 'You already have an account with that label') + return false + } else { + this._setErrorState('label', '') + return true + } + } + + _handleTextFieldChange = e => { + const key = e.target.id + + this._clearErrors(key) + this.setState({ + [key]: e.target.value, + }) + } + + handleSubmit = e => { + e.preventDefault() + + const { encryptedWif, passPhrase, label } = this.state + const { setAccount, addAccount, history } = this.props + + const validated = this._validateLabel(label) + console.log(validated) + if (validated) { + wallet + .decryptAsync(encryptedWif, passPhrase) + .then(wif => { + const account = new wallet.Account(wif) + + const accountObject = { + key: encryptedWif, + address: account.address, + label: label, + isDefault: false, + } + + addAccount(new wallet.Account(accountObject)) + setAccount(encryptedWif, account.address) + history.push('/home') + }) + .catch(() => this._setErrorState('passPhrase', 'Wrong password or encrypted key')) + } + } + + render() { + const { encryptedWif, passPhrase, label, errors } = this.state + return ( +
+ +

Log in with encrypted key

+
+ + + + + +
+
+ ) + } +} + +CreateWalletWithWif.propTypes = { + addAccount: PropTypes.func.isRequired, + setAccount: PropTypes.func.isRequired, + history: PropTypes.object.isRequired, + accounts: PropTypes.object.isRequired, +} + +export default CreateWalletWithWif diff --git a/src/app/containers/CreateWalletWithWif/index.js b/src/app/containers/CreateWalletWithWif/index.js new file mode 100644 index 0000000..1639230 --- /dev/null +++ b/src/app/containers/CreateWalletWithWif/index.js @@ -0,0 +1,21 @@ +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { withRouter } from 'react-router' + +import { addAccount } from '../../actions/wallet' +import { setAccount } from '../../actions/account' + +import CreateWalletWithWif from './CreateWalletWithWif' + +const actionCreators = { + setAccount, + addAccount, +} + +const mapStateToProps = state => ({ + accounts: state.wallet.accounts, +}) + +const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(CreateWalletWithWif)) From b709b3fa7e0c6c22347b4e41a8a603c43b661bd8 Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Sun, 15 Apr 2018 17:25:46 +0200 Subject: [PATCH 03/11] Write tests for create account with wif --- .../containers/CreateWalletWithWif.test.js | 91 +++++++++++++++++++ .../CreateWalletWithWif.js | 17 ++-- 2 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 __tests__/containers/CreateWalletWithWif.test.js diff --git a/__tests__/containers/CreateWalletWithWif.test.js b/__tests__/containers/CreateWalletWithWif.test.js new file mode 100644 index 0000000..2fc9de1 --- /dev/null +++ b/__tests__/containers/CreateWalletWithWif.test.js @@ -0,0 +1,91 @@ +import React from 'react' +import { wallet } from '@cityofzion/neon-js' + +import { mount } from 'enzyme' + +import CreateWalletWithWif from '../../src/app/containers/CreateWalletWithWif/CreateWalletWithWif' + +jest.useFakeTimers() + +const accounts = { + ARjkxk6VcKPFKqRHhuLNog9TbdYxhKu9be: { + address: 'ARjkxk6VcKPFKqRHhuLNog9TbdYxhKu9be', + isDefault: false, + key: '6PYRop1b45uKRUVUngUr3g44UmH8Kg6KTVTAvxyKKJLVpxQsM5HXUPrzCB', + label: 'TestKonto', + }, +} + +describe('CreateWallet', () => { + test('Creates valid credentials with manual WIF', done => { + const passphrase = 'cityofzion123' + + const preventDefault = jest.fn() + const addAccount = jest.fn() + + const wrapper = mount( + + ) + + wrapper.find('input#encryptedWif').simulate('change', { + target: { id: 'encryptedWif', value: '6PYQG2bP5dXuw2q6rLozSziezdA9E8esHhy2NmM1ZeSPdB2bWWAP87nb3i' }, + }) + wrapper.find('input#passPhrase').simulate('change', { target: { id: 'passPhrase', value: passphrase } }) + wrapper.find('input#label').simulate('change', { target: { id: 'label', value: 'somelabel' } }) + + console.log(wrapper.find('form')) + wrapper.find('form').simulate('submit', { preventDefault }) + + jest.runAllTimers() + + process.nextTick(() => { + expect(wrapper.state().errors.wif).toEqual('') + expect(wrapper.state().encryptedWif).toBeTruthy() + expect(wrapper.state().address).toBeTruthy() + + expect(wallet.isAddress(wrapper.state().address)).toEqual(true) + expect(wrapper.state().address).toEqual('AR4zoMWmbtP1M2YsYXQtDGyZJUxB9GwNZT') + expect(addAccount.mock.calls.length).toBe(1) + done() + }) + }) + + test('Shows error with invalid manual WIF', done => { + const passphrase = 'city of zion' + + const preventDefault = jest.fn() + + const wrapper = mount( + + ) + + wrapper.find('input#encryptedWif').simulate('change', { + target: { id: 'encryptedWif', value: '!xDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr' }, + }) + wrapper.find('input#passPhrase').simulate('change', { target: { id: 'passPhrase', value: passphrase } }) + wrapper.find('input#label').simulate('change', { target: { id: 'label', value: 'somelabel' } }) + + wrapper.find('form').simulate('submit', { preventDefault }) + + jest.runAllTimers() + + process.nextTick(() => { + console.log(wrapper.state()) + + expect(wrapper.state().errors.passPhrase).not.toEqual('') + done() + }) + }) +}) diff --git a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js index 10def44..7def442 100644 --- a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js +++ b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js @@ -14,6 +14,7 @@ class CreateWalletWithWif extends Component { state = { encryptedWif: '', passPhrase: '', + address: '', label: '', errors: { wif: '', @@ -78,7 +79,7 @@ class CreateWalletWithWif extends Component { const { setAccount, addAccount, history } = this.props const validated = this._validateLabel(label) - console.log(validated) + if (validated) { wallet .decryptAsync(encryptedWif, passPhrase) @@ -92,9 +93,11 @@ class CreateWalletWithWif extends Component { isDefault: false, } - addAccount(new wallet.Account(accountObject)) - setAccount(encryptedWif, account.address) - history.push('/home') + this.setState({ address: account.address, encryptedWif: wif }, () => { + addAccount(new wallet.Account(accountObject)) + setAccount(encryptedWif, account.address) + history.push('/home') + }) }) .catch(() => this._setErrorState('passPhrase', 'Wrong password or encrypted key')) } @@ -105,7 +108,7 @@ class CreateWalletWithWif extends Component { return (
-

Log in with encrypted key

+

Create wallet with encrypted key

- +
From db0ad065282e56310e803cdc60ea153e083581f0 Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Sun, 15 Apr 2018 16:37:44 +0200 Subject: [PATCH 04/11] Rewrite create account with wif --- __tests__/containers/CreateWallet.test.js | 27 +++- src/app/components/HoC/withCreateAccount.js | 64 ++++++++ src/app/containers/App/App.js | 3 +- .../containers/CreateWallet/CreateWallet.js | 26 ++- src/app/containers/CreateWallet/index.js | 6 +- .../CreateWalletWithWif.css | 19 +++ .../CreateWalletWithWif.js | 149 ++++++++++++++++++ .../containers/CreateWalletWithWif/index.js | 21 +++ 8 files changed, 302 insertions(+), 13 deletions(-) create mode 100644 src/app/components/HoC/withCreateAccount.js create mode 100644 src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css create mode 100644 src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js create mode 100644 src/app/containers/CreateWalletWithWif/index.js diff --git a/__tests__/containers/CreateWallet.test.js b/__tests__/containers/CreateWallet.test.js index 7b995e0..8443a09 100644 --- a/__tests__/containers/CreateWallet.test.js +++ b/__tests__/containers/CreateWallet.test.js @@ -8,9 +8,18 @@ import Loader from '../../src/app/components/Loader' jest.useFakeTimers() +const accounts = { + ARjkxk6VcKPFKqRHhuLNog9TbdYxhKu9be: { + address: 'ARjkxk6VcKPFKqRHhuLNog9TbdYxhKu9be', + isDefault: false, + key: '6PYRop1b45uKRUVUngUr3g44UmH8Kg6KTVTAvxyKKJLVpxQsM5HXUPrzCB', + label: 'TestKonto', + }, +} + describe('CreateWallet', () => { test('shows loading', () => { - const wrapper = shallow() + const wrapper = shallow() wrapper.setState({ loading: true }) expect(wrapper.find(Loader).length).toEqual(1) }) @@ -21,7 +30,9 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() const addAccount = jest.fn() - const wrapper = mount() + const wrapper = mount( + + ) wrapper .find('input#passPhraseConfirm') @@ -50,7 +61,7 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() - const wrapper = mount() + const wrapper = mount() wrapper .find('input#passPhraseConfirm') @@ -70,7 +81,7 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() - const wrapper = mount() + const wrapper = mount() wrapper .find('input#passPhraseConfirm') @@ -91,7 +102,9 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() const addAccount = jest.fn() - const wrapper = mount() + const wrapper = mount( + + ) wrapper .find('input#wif') @@ -126,7 +139,9 @@ describe('CreateWallet', () => { const preventDefault = jest.fn() - const wrapper = mount() + const wrapper = mount( + + ) wrapper .find('input#wif') diff --git a/src/app/components/HoC/withCreateAccount.js b/src/app/components/HoC/withCreateAccount.js new file mode 100644 index 0000000..d51c90e --- /dev/null +++ b/src/app/components/HoC/withCreateAccount.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react' +import { validateLength } from '../../utils/helpers' + +export function withCreateAccount(WrappedComponent, accounts) { + return class extends Component { + _clearErrors(key) { + this._setErrorState(key, '') + } + + _setErrorState = (key, value) => { + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [key]: value, + }, + })) + } + + _handleTextFieldChange = e => { + const key = e.target.id + + this._clearErrors(key) + this.setState({ + [key]: e.target.value, + }) + } + + _labelExists = label => { + const labelExists = Object.keys(accounts) + .map(account => { + return accounts[account.label] + }) + .find(accountLabel => accountLabel === label) + + return !!labelExists + } + + _validateLabel = () => { + const { label } = this.state + const labelExists = this._labelExists(label) + + if (!validateLength(label, 1)) { + this._setErrorState('label', 'Account name must be longer than 1.') + return false + } else if (!labelExists) { + this._setErrorState('label', 'You already have an account with that label') + return false + } else { + this._setErrorState('label', '') + return true + } + } + + render() { + return ( + + ) + } + } +} diff --git a/src/app/containers/App/App.js b/src/app/containers/App/App.js index 1d3f964..b21cf1e 100644 --- a/src/app/containers/App/App.js +++ b/src/app/containers/App/App.js @@ -7,6 +7,7 @@ import Send from '../Send' import TestInvoke from '../TestInvoke' import SendInvoke from '../SendInvoke' import CreateWallet from '../CreateWallet' +import CreateWalletWithWif from '../CreateWalletWithWif' import ImportWallet from '../ImportWallet' import ExportWallet from '../ExportWallet' import Config from '../Config' @@ -34,7 +35,7 @@ export default class App extends Component { - } /> + diff --git a/src/app/containers/CreateWallet/CreateWallet.js b/src/app/containers/CreateWallet/CreateWallet.js index cbfb7d0..45f2eaf 100644 --- a/src/app/containers/CreateWallet/CreateWallet.js +++ b/src/app/containers/CreateWallet/CreateWallet.js @@ -51,12 +51,27 @@ export default class CreateWallet extends Component { }) } + _labelExists = label => { + const { accounts } = this.props + const labelExists = Object.keys(accounts) + .map(account => { + return accounts[account].label + }) + .find(accountLabel => accountLabel === label) + + return !!labelExists + } + _validateLabel = () => { const { label } = this.state + const labelExists = this._labelExists(label) if (!validateLength(label, 1)) { this._setErrorState('label', 'Account name must be longer than 1.') return false + } else if (labelExists) { + this._setErrorState('label', 'You already have an account with that label') + return false } else { this._setErrorState('label', '') return true @@ -119,9 +134,9 @@ export default class CreateWallet extends Component { const { addAccount, manualWIF, setAccount } = this.props const validated = this._validate() - if (validated) { - // Make wallet.decrypt() async. + this.setState({ loading: true }) + const account = new wallet.Account(manualWIF ? wif : wallet.generatePrivateKey()) wallet @@ -142,9 +157,10 @@ export default class CreateWallet extends Component { encryptedWif: encryptedWif, address: account.address, }, - () => setAccount(account.WIF, account.address) + () => setAccount(encryptedWif, account.address) ) - }).catch(e => { + }) + .catch(e => { this.setState({ loading: false, errorMsg: e.message }) }) } @@ -157,7 +173,6 @@ export default class CreateWallet extends Component { if (loading) { return } else if (encryptedWif) { - // handle success return } @@ -214,4 +229,5 @@ CreateWallet.propTypes = { setAccount: PropTypes.func.isRequired, manualWIF: PropTypes.bool, history: PropTypes.object.isRequired, + accounts: PropTypes.object.isRequired, } diff --git a/src/app/containers/CreateWallet/index.js b/src/app/containers/CreateWallet/index.js index 40c1cff..833a6b4 100644 --- a/src/app/containers/CreateWallet/index.js +++ b/src/app/containers/CreateWallet/index.js @@ -12,6 +12,10 @@ const actionCreators = { setAccount, } +const mapStateToProps = state => ({ + accounts: state.wallet.accounts, +}) + const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) -export default withRouter(connect(null, mapDispatchToProps)(CreateWallet)) +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(CreateWallet)) diff --git a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css new file mode 100644 index 0000000..a4000e4 --- /dev/null +++ b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css @@ -0,0 +1,19 @@ +.createWalletWithWifWrapper { + background-color: #f1f1f1; + padding: 20px; + height: 100%; +} + +.createWalletWithWifHeading { + margin-bottom: 30px; + width: 80%; + text-align: center; +} + +.createWalletWithWifButton { + margin: 15px auto 0 auto; +} + +.createWalletWithWifForm { + width: 100%; +} diff --git a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js new file mode 100644 index 0000000..10def44 --- /dev/null +++ b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js @@ -0,0 +1,149 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +import { wallet } from '@cityofzion/neon-js' +import { validateLength } from '../../utils/helpers' + +import Box from '../../components/common/Box' +import InputField from '../../components/common/form/InputField' +import PrimaryButton from '../../components/common/buttons/PrimaryButton' + +import style from './CreateWalletWithWif.css' + +class CreateWalletWithWif extends Component { + state = { + encryptedWif: '', + passPhrase: '', + label: '', + errors: { + wif: '', + passPhrase: '', + label: '', + }, + } + + _clearErrors(key) { + this._setErrorState(key, '') + } + + _setErrorState = (key, value) => { + this.setState(prevState => ({ + errors: { + ...prevState.errors, + [key]: value, + }, + })) + } + + _labelExists = label => { + const { accounts } = this.props + const labelExists = Object.keys(accounts) + .map(account => { + return accounts[account].label + }) + .find(accountLabel => accountLabel === label) + + return !!labelExists + } + + _validateLabel = () => { + const { label } = this.state + const labelExists = this._labelExists(label) + + if (!validateLength(label, 1)) { + this._setErrorState('label', 'Account name must be longer than 1.') + return false + } else if (labelExists) { + this._setErrorState('label', 'You already have an account with that label') + return false + } else { + this._setErrorState('label', '') + return true + } + } + + _handleTextFieldChange = e => { + const key = e.target.id + + this._clearErrors(key) + this.setState({ + [key]: e.target.value, + }) + } + + handleSubmit = e => { + e.preventDefault() + + const { encryptedWif, passPhrase, label } = this.state + const { setAccount, addAccount, history } = this.props + + const validated = this._validateLabel(label) + console.log(validated) + if (validated) { + wallet + .decryptAsync(encryptedWif, passPhrase) + .then(wif => { + const account = new wallet.Account(wif) + + const accountObject = { + key: encryptedWif, + address: account.address, + label: label, + isDefault: false, + } + + addAccount(new wallet.Account(accountObject)) + setAccount(encryptedWif, account.address) + history.push('/home') + }) + .catch(() => this._setErrorState('passPhrase', 'Wrong password or encrypted key')) + } + } + + render() { + const { encryptedWif, passPhrase, label, errors } = this.state + return ( +
+ +

Log in with encrypted key

+
+ + + + + +
+
+ ) + } +} + +CreateWalletWithWif.propTypes = { + addAccount: PropTypes.func.isRequired, + setAccount: PropTypes.func.isRequired, + history: PropTypes.object.isRequired, + accounts: PropTypes.object.isRequired, +} + +export default CreateWalletWithWif diff --git a/src/app/containers/CreateWalletWithWif/index.js b/src/app/containers/CreateWalletWithWif/index.js new file mode 100644 index 0000000..1639230 --- /dev/null +++ b/src/app/containers/CreateWalletWithWif/index.js @@ -0,0 +1,21 @@ +import { connect } from 'react-redux' +import { bindActionCreators } from 'redux' +import { withRouter } from 'react-router' + +import { addAccount } from '../../actions/wallet' +import { setAccount } from '../../actions/account' + +import CreateWalletWithWif from './CreateWalletWithWif' + +const actionCreators = { + setAccount, + addAccount, +} + +const mapStateToProps = state => ({ + accounts: state.wallet.accounts, +}) + +const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(CreateWalletWithWif)) From d19a923f5bbda453c02747ed4ae6d2355eb98241 Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Sun, 15 Apr 2018 17:25:46 +0200 Subject: [PATCH 05/11] Write tests for create account with wif --- .../containers/CreateWalletWithWif.test.js | 91 +++++++++++++++++++ .../CreateWalletWithWif.js | 17 ++-- 2 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 __tests__/containers/CreateWalletWithWif.test.js diff --git a/__tests__/containers/CreateWalletWithWif.test.js b/__tests__/containers/CreateWalletWithWif.test.js new file mode 100644 index 0000000..2fc9de1 --- /dev/null +++ b/__tests__/containers/CreateWalletWithWif.test.js @@ -0,0 +1,91 @@ +import React from 'react' +import { wallet } from '@cityofzion/neon-js' + +import { mount } from 'enzyme' + +import CreateWalletWithWif from '../../src/app/containers/CreateWalletWithWif/CreateWalletWithWif' + +jest.useFakeTimers() + +const accounts = { + ARjkxk6VcKPFKqRHhuLNog9TbdYxhKu9be: { + address: 'ARjkxk6VcKPFKqRHhuLNog9TbdYxhKu9be', + isDefault: false, + key: '6PYRop1b45uKRUVUngUr3g44UmH8Kg6KTVTAvxyKKJLVpxQsM5HXUPrzCB', + label: 'TestKonto', + }, +} + +describe('CreateWallet', () => { + test('Creates valid credentials with manual WIF', done => { + const passphrase = 'cityofzion123' + + const preventDefault = jest.fn() + const addAccount = jest.fn() + + const wrapper = mount( + + ) + + wrapper.find('input#encryptedWif').simulate('change', { + target: { id: 'encryptedWif', value: '6PYQG2bP5dXuw2q6rLozSziezdA9E8esHhy2NmM1ZeSPdB2bWWAP87nb3i' }, + }) + wrapper.find('input#passPhrase').simulate('change', { target: { id: 'passPhrase', value: passphrase } }) + wrapper.find('input#label').simulate('change', { target: { id: 'label', value: 'somelabel' } }) + + console.log(wrapper.find('form')) + wrapper.find('form').simulate('submit', { preventDefault }) + + jest.runAllTimers() + + process.nextTick(() => { + expect(wrapper.state().errors.wif).toEqual('') + expect(wrapper.state().encryptedWif).toBeTruthy() + expect(wrapper.state().address).toBeTruthy() + + expect(wallet.isAddress(wrapper.state().address)).toEqual(true) + expect(wrapper.state().address).toEqual('AR4zoMWmbtP1M2YsYXQtDGyZJUxB9GwNZT') + expect(addAccount.mock.calls.length).toBe(1) + done() + }) + }) + + test('Shows error with invalid manual WIF', done => { + const passphrase = 'city of zion' + + const preventDefault = jest.fn() + + const wrapper = mount( + + ) + + wrapper.find('input#encryptedWif').simulate('change', { + target: { id: 'encryptedWif', value: '!xDgvEKzgSBPPfuVfw67oPQBSjidEiqTHURKSDL1R7yGaGYAeYnr' }, + }) + wrapper.find('input#passPhrase').simulate('change', { target: { id: 'passPhrase', value: passphrase } }) + wrapper.find('input#label').simulate('change', { target: { id: 'label', value: 'somelabel' } }) + + wrapper.find('form').simulate('submit', { preventDefault }) + + jest.runAllTimers() + + process.nextTick(() => { + console.log(wrapper.state()) + + expect(wrapper.state().errors.passPhrase).not.toEqual('') + done() + }) + }) +}) diff --git a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js index 10def44..7def442 100644 --- a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js +++ b/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js @@ -14,6 +14,7 @@ class CreateWalletWithWif extends Component { state = { encryptedWif: '', passPhrase: '', + address: '', label: '', errors: { wif: '', @@ -78,7 +79,7 @@ class CreateWalletWithWif extends Component { const { setAccount, addAccount, history } = this.props const validated = this._validateLabel(label) - console.log(validated) + if (validated) { wallet .decryptAsync(encryptedWif, passPhrase) @@ -92,9 +93,11 @@ class CreateWalletWithWif extends Component { isDefault: false, } - addAccount(new wallet.Account(accountObject)) - setAccount(encryptedWif, account.address) - history.push('/home') + this.setState({ address: account.address, encryptedWif: wif }, () => { + addAccount(new wallet.Account(accountObject)) + setAccount(encryptedWif, account.address) + history.push('/home') + }) }) .catch(() => this._setErrorState('passPhrase', 'Wrong password or encrypted key')) } @@ -105,7 +108,7 @@ class CreateWalletWithWif extends Component { return (
-

Log in with encrypted key

+

Create wallet with encrypted key

- +
From b7de60cdb146239247c1a2ad4c991db66bb4ee45 Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Mon, 16 Apr 2018 19:54:17 +0200 Subject: [PATCH 06/11] Add second home screen in order to accommodate more choices --- .../CreateAccountStartPage/index.js | 30 +++++++++++++ src/app/components/StartPage/StartPage.css | 4 ++ src/app/components/StartPage/index.js | 6 +-- src/app/containers/App/App.js | 7 ++- .../containers/CreateWallet/CreateWallet.js | 18 ++------ .../CreateWalletWithEncryptedWif.js} | 43 ++++++++++--------- .../CreateWalletWithWif.css | 0 .../index.js | 4 +- src/app/utils/helpers.js | 10 +++++ 9 files changed, 78 insertions(+), 44 deletions(-) create mode 100644 src/app/components/CreateAccountStartPage/index.js rename src/app/containers/{CreateWalletWithWif/CreateWalletWithWif.js => CreateWalletWithEncryptedWif/CreateWalletWithEncryptedWif.js} (82%) rename src/app/containers/{CreateWalletWithWif => CreateWalletWithEncryptedWif}/CreateWalletWithWif.css (100%) rename src/app/containers/{CreateWalletWithWif => CreateWalletWithEncryptedWif}/index.js (81%) diff --git a/src/app/components/CreateAccountStartPage/index.js b/src/app/components/CreateAccountStartPage/index.js new file mode 100644 index 0000000..b99fdeb --- /dev/null +++ b/src/app/components/CreateAccountStartPage/index.js @@ -0,0 +1,30 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { withRouter } from 'react-router-dom' + +import style from '../../components/StartPage/StartPage.css' + +const CreateAccountStartPage = ({ history }) => { + return ( +
+ + + +
+ ) +} + +CreateAccountStartPage.propTypes = { + history: PropTypes.object, +} + +export default withRouter(CreateAccountStartPage) diff --git a/src/app/components/StartPage/StartPage.css b/src/app/components/StartPage/StartPage.css index 4446b85..682a1be 100644 --- a/src/app/components/StartPage/StartPage.css +++ b/src/app/components/StartPage/StartPage.css @@ -6,6 +6,10 @@ display: flex; flex-direction: column; align-items: center; + padding-top: 122px; +} + +.startPageCreateAccount { padding-top: 80px; } diff --git a/src/app/components/StartPage/index.js b/src/app/components/StartPage/index.js index ca051e4..1299728 100644 --- a/src/app/components/StartPage/index.js +++ b/src/app/components/StartPage/index.js @@ -7,7 +7,7 @@ import style from './StartPage.css' const StartPage = ({ history }) => { return (
- @@ -15,10 +15,6 @@ const StartPage = ({ history }) => { Use Saved Wallet -
) } diff --git a/src/app/containers/App/App.js b/src/app/containers/App/App.js index b21cf1e..2b7f6b4 100644 --- a/src/app/containers/App/App.js +++ b/src/app/containers/App/App.js @@ -7,12 +7,13 @@ import Send from '../Send' import TestInvoke from '../TestInvoke' import SendInvoke from '../SendInvoke' import CreateWallet from '../CreateWallet' -import CreateWalletWithWif from '../CreateWalletWithWif' +import CreateWalletWithEncryptedWif from '../CreateWalletWithEncryptedWif' import ImportWallet from '../ImportWallet' import ExportWallet from '../ExportWallet' import Config from '../Config' import ContentWrapper from '../../components/ContentWrapper' import StartPage from '../../components/StartPage' +import CreateAccountStartPage from '../../components/CreateAccountStartPage' import Login from '../../components/Login' import Header from '../../components/Header' @@ -32,10 +33,12 @@ export default class App extends Component { + - + + } /> diff --git a/src/app/containers/CreateWallet/CreateWallet.js b/src/app/containers/CreateWallet/CreateWallet.js index 45f2eaf..5af208c 100644 --- a/src/app/containers/CreateWallet/CreateWallet.js +++ b/src/app/containers/CreateWallet/CreateWallet.js @@ -7,7 +7,7 @@ import InputField from '../../components/common/form/InputField' import Box from '../../components/common/Box' import CreateWalletSucessPage from '../../components/successPages/CreateWalletSuccessPage' -import { validateLength } from '../../utils/helpers' +import { validateLength, labelExists } from '../../utils/helpers' import style from './CreateWallet.css' import Loader from '../../components/Loader' @@ -51,25 +51,15 @@ export default class CreateWallet extends Component { }) } - _labelExists = label => { - const { accounts } = this.props - const labelExists = Object.keys(accounts) - .map(account => { - return accounts[account].label - }) - .find(accountLabel => accountLabel === label) - - return !!labelExists - } - _validateLabel = () => { const { label } = this.state - const labelExists = this._labelExists(label) + const { accounts } = this.props + const labelDeclared = labelExists(label, accounts) if (!validateLength(label, 1)) { this._setErrorState('label', 'Account name must be longer than 1.') return false - } else if (labelExists) { + } else if (labelDeclared) { this._setErrorState('label', 'You already have an account with that label') return false } else { diff --git a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js b/src/app/containers/CreateWalletWithEncryptedWif/CreateWalletWithEncryptedWif.js similarity index 82% rename from src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js rename to src/app/containers/CreateWalletWithEncryptedWif/CreateWalletWithEncryptedWif.js index 7def442..84ed5e5 100644 --- a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.js +++ b/src/app/containers/CreateWalletWithEncryptedWif/CreateWalletWithEncryptedWif.js @@ -2,16 +2,18 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { wallet } from '@cityofzion/neon-js' -import { validateLength } from '../../utils/helpers' +import { validateLength, labelExists } from '../../utils/helpers' import Box from '../../components/common/Box' import InputField from '../../components/common/form/InputField' import PrimaryButton from '../../components/common/buttons/PrimaryButton' +import Loader from '../../components/Loader' import style from './CreateWalletWithWif.css' -class CreateWalletWithWif extends Component { +class CreateWalletWitEncryptedWif extends Component { state = { + loading: false, encryptedWif: '', passPhrase: '', address: '', @@ -36,25 +38,15 @@ class CreateWalletWithWif extends Component { })) } - _labelExists = label => { - const { accounts } = this.props - const labelExists = Object.keys(accounts) - .map(account => { - return accounts[account].label - }) - .find(accountLabel => accountLabel === label) - - return !!labelExists - } - _validateLabel = () => { const { label } = this.state - const labelExists = this._labelExists(label) + const { accounts } = this.props + const labelDeclared = labelExists(label, accounts) if (!validateLength(label, 1)) { this._setErrorState('label', 'Account name must be longer than 1.') return false - } else if (labelExists) { + } else if (labelDeclared) { this._setErrorState('label', 'You already have an account with that label') return false } else { @@ -81,6 +73,7 @@ class CreateWalletWithWif extends Component { const validated = this._validateLabel(label) if (validated) { + this.setState({ loading: true }) wallet .decryptAsync(encryptedWif, passPhrase) .then(wif => { @@ -93,19 +86,25 @@ class CreateWalletWithWif extends Component { isDefault: false, } - this.setState({ address: account.address, encryptedWif: wif }, () => { + this.setState({ address: account.address, encryptedWif: wif, loading: false }, () => { addAccount(new wallet.Account(accountObject)) setAccount(encryptedWif, account.address) history.push('/home') }) }) - .catch(() => this._setErrorState('passPhrase', 'Wrong password or encrypted key')) + .catch(() => { + this.setState({ loading: false }) + this._setErrorState('passPhrase', 'Wrong password or encrypted key') + }) } } render() { - const { encryptedWif, passPhrase, label, errors } = this.state - return ( + const { encryptedWif, passPhrase, label, errors, loading } = this.state + + const content = loading ? ( + + ) : (

Create wallet with encrypted key

@@ -139,14 +138,16 @@ class CreateWalletWithWif extends Component {
) + + return content } } -CreateWalletWithWif.propTypes = { +CreateWalletWitEncryptedWif.propTypes = { addAccount: PropTypes.func.isRequired, setAccount: PropTypes.func.isRequired, history: PropTypes.object.isRequired, accounts: PropTypes.object.isRequired, } -export default CreateWalletWithWif +export default CreateWalletWitEncryptedWif diff --git a/src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css b/src/app/containers/CreateWalletWithEncryptedWif/CreateWalletWithWif.css similarity index 100% rename from src/app/containers/CreateWalletWithWif/CreateWalletWithWif.css rename to src/app/containers/CreateWalletWithEncryptedWif/CreateWalletWithWif.css diff --git a/src/app/containers/CreateWalletWithWif/index.js b/src/app/containers/CreateWalletWithEncryptedWif/index.js similarity index 81% rename from src/app/containers/CreateWalletWithWif/index.js rename to src/app/containers/CreateWalletWithEncryptedWif/index.js index 1639230..9f39bc4 100644 --- a/src/app/containers/CreateWalletWithWif/index.js +++ b/src/app/containers/CreateWalletWithEncryptedWif/index.js @@ -5,7 +5,7 @@ import { withRouter } from 'react-router' import { addAccount } from '../../actions/wallet' import { setAccount } from '../../actions/account' -import CreateWalletWithWif from './CreateWalletWithWif' +import CreateWalletWithEncryptedWif from './CreateWalletWithEncryptedWif' const actionCreators = { setAccount, @@ -18,4 +18,4 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => bindActionCreators(actionCreators, dispatch) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(CreateWalletWithWif)) +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(CreateWalletWithEncryptedWif)) diff --git a/src/app/utils/helpers.js b/src/app/utils/helpers.js index 43f7b78..acedbc7 100644 --- a/src/app/utils/helpers.js +++ b/src/app/utils/helpers.js @@ -17,6 +17,16 @@ export const validateLength = (input, minLength) => { return true } +export const labelExists = (label, accounts) => { + const labelExists = Object.keys(accounts) + .map(account => { + return accounts[account].label + }) + .find(accountLabel => accountLabel.toLowerCase() === label.toLowerCase()) + + return !!labelExists +} + export const getBalance = (networks, network, account) => { return new Promise((resolve, reject) => { api[networks[network].apiType] From 4fee1e45806c5e9ada62604eac36b4e4991d0dc5 Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Mon, 16 Apr 2018 19:55:42 +0200 Subject: [PATCH 07/11] Update tests --- ...thWif.test.js => CreateWalletWithEncryptedWif.test.js} | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) rename __tests__/containers/{CreateWalletWithWif.test.js => CreateWalletWithEncryptedWif.test.js} (92%) diff --git a/__tests__/containers/CreateWalletWithWif.test.js b/__tests__/containers/CreateWalletWithEncryptedWif.test.js similarity index 92% rename from __tests__/containers/CreateWalletWithWif.test.js rename to __tests__/containers/CreateWalletWithEncryptedWif.test.js index 2fc9de1..044d4c4 100644 --- a/__tests__/containers/CreateWalletWithWif.test.js +++ b/__tests__/containers/CreateWalletWithEncryptedWif.test.js @@ -3,7 +3,7 @@ import { wallet } from '@cityofzion/neon-js' import { mount } from 'enzyme' -import CreateWalletWithWif from '../../src/app/containers/CreateWalletWithWif/CreateWalletWithWif' +import CreateWalletWithEncryptedWif from '../../src/app/containers/CreateWalletWithEncryptedWif/CreateWalletWithEncryptedWif' jest.useFakeTimers() @@ -24,7 +24,7 @@ describe('CreateWallet', () => { const addAccount = jest.fn() const wrapper = mount( - { const preventDefault = jest.fn() const wrapper = mount( - { jest.runAllTimers() process.nextTick(() => { - console.log(wrapper.state()) - expect(wrapper.state().errors.passPhrase).not.toEqual('') done() }) From 373ce440a5c3289fc52d6afc9c508f1c57ccd978 Mon Sep 17 00:00:00 2001 From: FredrikOseberg Date: Tue, 17 Apr 2018 22:26:25 +0200 Subject: [PATCH 08/11] Fix typo in create wallet --- src/app/components/CreateAccountStartPage/index.js | 2 +- src/app/utils/helpers.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/components/CreateAccountStartPage/index.js b/src/app/components/CreateAccountStartPage/index.js index b99fdeb..253d8a6 100644 --- a/src/app/components/CreateAccountStartPage/index.js +++ b/src/app/components/CreateAccountStartPage/index.js @@ -9,7 +9,7 @@ const CreateAccountStartPage = ({ history }) => {