diff --git a/js/src/modals/AddContract/addContract.js b/js/src/modals/AddContract/addContract.js
index 0d04438c3..c5e7aac61 100644
--- a/js/src/modals/AddContract/addContract.js
+++ b/js/src/modals/AddContract/addContract.js
@@ -14,38 +14,17 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react';
-import ContentAdd from 'material-ui/svg-icons/content/add';
-import ContentClear from 'material-ui/svg-icons/content/clear';
-import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
-import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
+import { FormattedMessage } from 'react-intl';
+import { newError } from '~/redux/actions';
import { Button, Modal, Form, Input, InputAddress, RadioButtons } from '~/ui';
-import { ERRORS, validateAbi, validateAddress, validateName } from '~/util/validation';
+import { AddIcon, CancelIcon, NextIcon, PrevIcon } from '~/ui/Icons';
-import { eip20, wallet } from '~/contracts/abi';
-
-const ABI_TYPES = [
- {
- label: 'Token', readOnly: true, value: JSON.stringify(eip20),
- type: 'token',
- description: (A standard ERC 20 token)
- },
- {
- label: 'Multisig Wallet', readOnly: true,
- type: 'multisig',
- value: JSON.stringify(wallet),
- description: (Official Multisig contract: see contract code)
- },
- {
- label: 'Custom Contract', value: '',
- type: 'custom',
- description: 'Contract created from custom ABI'
- }
-];
-
-const STEPS = [ 'choose a contract type', 'enter contract details' ];
+import Store from './store';
+@observer
export default class AddContract extends Component {
static contextTypes = {
api: PropTypes.object.isRequired
@@ -56,219 +35,217 @@ export default class AddContract extends Component {
onClose: PropTypes.func
};
- state = {
- abi: '',
- abiError: ERRORS.invalidAbi,
- abiType: ABI_TYPES[2],
- abiTypeIndex: 2,
- abiParsed: null,
- address: '',
- addressError: ERRORS.invalidAddress,
- name: '',
- nameError: ERRORS.invalidName,
- description: '',
- step: 0
- };
-
- componentDidMount () {
- this.onChangeABIType(null, this.state.abiTypeIndex);
- }
+ store = new Store(this.context.api, this.props.contracts);
render () {
- const { step } = this.state;
+ const { step } = this.store;
return (
- { this.renderStep(step) }
+ steps={ [
+ ,
+
+ ] }
+ visible>
+ { this.renderStep() }
);
}
- renderStep (step) {
+ renderStep () {
+ const { step } = this.store;
+
switch (step) {
case 0:
return this.renderContractTypeSelector();
+
default:
return this.renderFields();
}
}
renderContractTypeSelector () {
- const { abiTypeIndex } = this.state;
+ const { abiTypeIndex, abiTypes } = this.store;
return (
);
}
renderDialogActions () {
- const { addressError, nameError, step } = this.state;
- const hasError = !!(addressError || nameError);
+ const { step } = this.store;
const cancelBtn = (
}
- label='Cancel'
+ icon={ }
+ key='cancel'
+ label={
+
+ }
onClick={ this.onClose } />
);
if (step === 0) {
- const nextBtn = (
+ return [
+ cancelBtn,
}
- label='Next'
+ icon={ }
+ key='next'
+ label={
+
+ }
onClick={ this.onNext } />
- );
-
- return [ cancelBtn, nextBtn ];
+ ];
}
- const prevBtn = (
+ return [
+ cancelBtn,
}
- label='Back'
- onClick={ this.onPrev } />
- );
-
- const addBtn = (
+ icon={ }
+ key='prev'
+ label={
+
+ }
+ onClick={ this.onPrev } />,
}
- label='Add Contract'
- disabled={ hasError }
+ icon={ }
+ key='add'
+ label={
+
+ }
+ disabled={ this.store.hasError }
onClick={ this.onAdd } />
- );
-
- return [ cancelBtn, prevBtn, addBtn ];
+ ];
}
renderFields () {
- const { abi, abiError, address, addressError, description, name, nameError, abiType } = this.state;
+ const { abi, abiError, abiType, address, addressError, description, name, nameError } = this.store;
return (
);
}
- getAbiTypes () {
- return ABI_TYPES.map((type, index) => ({
- label: type.label,
- description: type.description,
- key: index,
- ...type
- }));
- }
-
onNext = () => {
- this.setState({ step: this.state.step + 1 });
+ this.store.nextStep();
}
onPrev = () => {
- this.setState({ step: this.state.step - 1 });
+ this.store.prevStep();
}
onChangeABIType = (value, index) => {
- const abiType = value || ABI_TYPES[index];
- this.setState({ abiTypeIndex: index, abiType });
- this.onEditAbi(abiType.value);
+ this.store.setAbiTypeIndex(index);
}
- onEditAbi = (abiIn) => {
- const { api } = this.context;
- const { abi, abiError, abiParsed } = validateAbi(abiIn, api);
-
- this.setState({ abi, abiError, abiParsed });
+ onEditAbi = (abi) => {
+ this.store.setAbi(abi);
}
- onChangeAddress = (event, value) => {
- this.onEditAddress(value);
+ onChangeAddress = (event, address) => {
+ this.onEditAddress(address);
}
- onEditAddress = (_address) => {
- const { contracts } = this.props;
- let { address, addressError } = validateAddress(_address);
-
- if (!addressError) {
- const contract = contracts[address];
-
- if (contract) {
- addressError = ERRORS.duplicateAddress;
- }
- }
-
- this.setState({
- address,
- addressError
- });
+ onEditAddress = (address) => {
+ this.store.setAddress(address);
}
onEditDescription = (description) => {
- this.setState({ description });
+ this.store.setDescription(description);
}
onEditName = (name) => {
- this.setState(validateName(name));
+ this.store.setName(name);
}
onAdd = () => {
- const { api } = this.context;
- const { abiParsed, address, name, description, abiType } = this.state;
-
- Promise.all([
- api.parity.setAccountName(address, name),
- api.parity.setAccountMeta(address, {
- contract: true,
- deleted: false,
- timestamp: Date.now(),
- abi: abiParsed,
- type: abiType.type,
- description
+ return this.store
+ .addContract()
+ .then(() => {
+ this.onClose();
})
- ]).catch((error) => {
- console.error('onAdd', error);
- });
-
- this.props.onClose();
+ .catch((error) => {
+ newError(error);
+ });
}
onClose = () => {
diff --git a/js/src/modals/AddContract/addContract.spec.js b/js/src/modals/AddContract/addContract.spec.js
new file mode 100644
index 000000000..d65e06f67
--- /dev/null
+++ b/js/src/modals/AddContract/addContract.spec.js
@@ -0,0 +1,55 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import { shallow } from 'enzyme';
+import React from 'react';
+import sinon from 'sinon';
+
+import AddContract from './';
+
+import { CONTRACTS, createApi } from './addContract.test.js';
+
+let component;
+let onClose;
+
+function renderShallow (props) {
+ onClose = sinon.stub();
+ component = shallow(
+ ,
+ {
+ context: {
+ api: createApi()
+ }
+ }
+ );
+
+ return component;
+}
+
+describe('modals/AddContract', () => {
+ describe('rendering', () => {
+ beforeEach(() => {
+ renderShallow();
+ });
+
+ it('renders the defauls', () => {
+ expect(component).to.be.ok;
+ });
+ });
+});
diff --git a/js/src/modals/AddContract/addContract.test.js b/js/src/modals/AddContract/addContract.test.js
new file mode 100644
index 000000000..5dc2af293
--- /dev/null
+++ b/js/src/modals/AddContract/addContract.test.js
@@ -0,0 +1,38 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import sinon from 'sinon';
+
+const ABI = '[{"constant":true,"inputs":[],"name":"totalDonated","outputs":[{"name":"","type":"uint256"}],"type":"function"}]';
+
+const CONTRACTS = {
+ '0x1234567890123456789012345678901234567890': {}
+};
+
+function createApi () {
+ return {
+ parity: {
+ setAccountMeta: sinon.stub().resolves(),
+ setAccountName: sinon.stub().resolves()
+ }
+ };
+}
+
+export {
+ ABI,
+ CONTRACTS,
+ createApi
+};
diff --git a/js/src/modals/AddContract/store.js b/js/src/modals/AddContract/store.js
new file mode 100644
index 000000000..d69df7be1
--- /dev/null
+++ b/js/src/modals/AddContract/store.js
@@ -0,0 +1,126 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import { action, computed, observable, transaction } from 'mobx';
+
+import { ERRORS, validateAbi, validateAddress, validateName } from '~/util/validation';
+
+import { ABI_TYPES } from './types';
+
+export default class Store {
+ @observable abi = '';
+ @observable abiError = ERRORS.invalidAbi;
+ @observable abiParsed = null;
+ @observable abiTypes = ABI_TYPES;
+ @observable abiTypeIndex = 0;
+ @observable address = '';
+ @observable addressError = ERRORS.invalidAddress;
+ @observable description = '';
+ @observable name = '';
+ @observable nameError = ERRORS.invalidName;
+ @observable step = 0;
+
+ constructor (api, contracts) {
+ this._api = api;
+ this._contracts = contracts;
+
+ this.setAbiTypeIndex(2);
+ }
+
+ @computed get abiType () {
+ return this.abiTypes[this.abiTypeIndex];
+ }
+
+ @computed get hasError () {
+ return !!(this.abiError || this.addressError || this.nameError);
+ }
+
+ @action nextStep = () => {
+ this.step++;
+ }
+
+ @action prevStep = () => {
+ this.step--;
+ }
+
+ @action setAbi = (_abi) => {
+ const { abi, abiError, abiParsed } = validateAbi(_abi);
+
+ transaction(() => {
+ this.abi = abi;
+ this.abiError = abiError;
+ this.abiParsed = abiParsed;
+ });
+ }
+
+ @action setAbiTypeIndex = (abiTypeIndex) => {
+ transaction(() => {
+ this.abiTypeIndex = abiTypeIndex;
+ this.setAbi(this.abiTypes[abiTypeIndex].value);
+ });
+ }
+
+ @action setAddress = (_address) => {
+ let { address, addressError } = validateAddress(_address);
+
+ if (!addressError) {
+ const contract = this._contracts[address];
+
+ if (contract) {
+ addressError = ERRORS.duplicateAddress;
+ }
+ }
+
+ transaction(() => {
+ this.address = address;
+ this.addressError = addressError;
+ });
+ }
+
+ @action setDescription = (description) => {
+ this.description = description;
+ }
+
+ @action setName = (_name) => {
+ const { name, nameError } = validateName(_name);
+
+ transaction(() => {
+ this.name = name;
+ this.nameError = nameError;
+ });
+ }
+
+ addContract () {
+ const meta = {
+ contract: true,
+ deleted: false,
+ timestamp: Date.now(),
+ abi: this.abiParsed,
+ type: this.abiType.type,
+ description: this.description
+ };
+
+ return Promise
+ .all([
+ this._api.parity.setAccountName(this.address, this.name),
+ this._api.parity.setAccountMeta(this.address, meta)
+ ])
+ .catch((error) => {
+ console.error('addContract', error);
+ throw error;
+ });
+ }
+}
diff --git a/js/src/modals/AddContract/store.spec.js b/js/src/modals/AddContract/store.spec.js
new file mode 100644
index 000000000..e309ac4f6
--- /dev/null
+++ b/js/src/modals/AddContract/store.spec.js
@@ -0,0 +1,171 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import Store from './store';
+
+import { ABI, CONTRACTS, createApi } from './addContract.test.js';
+
+const INVALID_ADDR = '0x123';
+const VALID_ADDR = '0x5A5eFF38DA95b0D58b6C616f2699168B480953C9';
+const DUPE_ADDR = Object.keys(CONTRACTS)[0];
+
+let api;
+let store;
+
+function createStore () {
+ api = createApi();
+ store = new Store(api, CONTRACTS);
+}
+
+describe('modals/AddContract/Store', () => {
+ beforeEach(() => {
+ createStore();
+ });
+
+ describe('constructor', () => {
+ it('creates an instance', () => {
+ expect(store).to.be.ok;
+ });
+
+ it('defaults to custom ABI', () => {
+ expect(store.abiType.type).to.equal('custom');
+ });
+ });
+
+ describe('@actions', () => {
+ describe('nextStep/prevStep', () => {
+ it('moves to the next/prev step', () => {
+ expect(store.step).to.equal(0);
+ store.nextStep();
+ expect(store.step).to.equal(1);
+ store.prevStep();
+ expect(store.step).to.equal(0);
+ });
+ });
+
+ describe('setAbiTypeIndex', () => {
+ beforeEach(() => {
+ store.setAbiTypeIndex(1);
+ });
+
+ it('changes the index', () => {
+ expect(store.abiTypeIndex).to.equal(1);
+ });
+
+ it('changes the abi', () => {
+ expect(store.abi).to.deep.equal(store.abiTypes[1].value);
+ });
+ });
+
+ describe('setAddress', () => {
+ it('sets a valid address', () => {
+ store.setAddress(VALID_ADDR);
+ expect(store.address).to.equal(VALID_ADDR);
+ expect(store.addressError).to.be.null;
+ });
+
+ it('sets the error on invalid address', () => {
+ store.setAddress(INVALID_ADDR);
+ expect(store.address).to.equal(INVALID_ADDR);
+ expect(store.addressError).not.to.be.null;
+ });
+
+ it('sets the error on suplicate address', () => {
+ store.setAddress(DUPE_ADDR);
+ expect(store.address).to.equal(DUPE_ADDR);
+ expect(store.addressError).not.to.be.null;
+ });
+ });
+
+ describe('setDescription', () => {
+ it('sets the description', () => {
+ store.setDescription('test description');
+ expect(store.description).to.equal('test description');
+ });
+ });
+
+ describe('setName', () => {
+ it('sets the name', () => {
+ store.setName('some name');
+ expect(store.name).to.equal('some name');
+ expect(store.nameError).to.be.null;
+ });
+
+ it('sets the error', () => {
+ store.setName('s');
+ expect(store.name).to.equal('s');
+ expect(store.nameError).not.to.be.null;
+ });
+ });
+ });
+
+ describe('@computed', () => {
+ describe('abiType', () => {
+ it('matches the index', () => {
+ expect(store.abiType).to.deep.equal(store.abiTypes[2]);
+ });
+ });
+
+ describe('hasError', () => {
+ beforeEach(() => {
+ store.setAddress(VALID_ADDR);
+ store.setName('valid name');
+ store.setAbi(ABI);
+ });
+
+ it('is false with no errors', () => {
+ expect(store.hasError).to.be.false;
+ });
+
+ it('is true with address error', () => {
+ store.setAddress(DUPE_ADDR);
+ expect(store.hasError).to.be.true;
+ });
+
+ it('is true with name error', () => {
+ store.setName('s');
+ expect(store.hasError).to.be.true;
+ });
+
+ it('is true with abi error', () => {
+ store.setAbi('');
+ expect(store.hasError).to.be.true;
+ });
+ });
+ });
+
+ describe('interactions', () => {
+ describe('addContract', () => {
+ beforeEach(() => {
+ store.setAddress(VALID_ADDR);
+ store.setName('valid name');
+ store.setAbi(ABI);
+ });
+
+ it('sets the account name', () => {
+ return store.addContract().then(() => {
+ expect(api.parity.setAccountName).to.have.been.calledWith(VALID_ADDR, 'valid name');
+ });
+ });
+
+ it('sets the account meta', () => {
+ return store.addContract().then(() => {
+ expect(api.parity.setAccountMeta).to.have.been.called;
+ });
+ });
+ });
+ });
+});
diff --git a/js/src/modals/AddContract/types.js b/js/src/modals/AddContract/types.js
new file mode 100644
index 000000000..9f64173f6
--- /dev/null
+++ b/js/src/modals/AddContract/types.js
@@ -0,0 +1,81 @@
+// Copyright 2015, 2016 Parity Technologies (UK) Ltd.
+// This file is part of Parity.
+
+// Parity is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity. If not, see .
+
+import React from 'react';
+import { FormattedMessage } from 'react-intl';
+
+import { eip20, wallet } from '~/contracts/abi';
+
+const ABI_TYPES = [
+ {
+ description:
+
+
+
+ } } />,
+ label:
+ ,
+ readOnly: true,
+ type: 'token',
+ value: JSON.stringify(eip20)
+ },
+ {
+ description:
+
+
+
+ } } />,
+ label:
+ ,
+ readOnly: true,
+ type: 'multisig',
+ value: JSON.stringify(wallet)
+ },
+ {
+ description:
+ ,
+ label:
+ ,
+ type: 'custom',
+ value: ''
+ }
+];
+
+export {
+ ABI_TYPES
+};
diff --git a/js/src/ui/Form/RadioButtons/radioButtons.js b/js/src/ui/Form/RadioButtons/radioButtons.js
index d9489c6aa..e18a884be 100644
--- a/js/src/ui/Form/RadioButtons/radioButtons.js
+++ b/js/src/ui/Form/RadioButtons/radioButtons.js
@@ -14,19 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see .
+import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
import React, { Component, PropTypes } from 'react';
-import { RadioButton, RadioButtonGroup } from 'material-ui/RadioButton';
-
+import { arrayOrObjectProptype } from '~/util/proptypes';
import styles from './radioButtons.css';
export default class RadioButtons extends Component {
static propTypes = {
+ name: PropTypes.string,
onChange: PropTypes.func.isRequired,
- values: PropTypes.array.isRequired,
-
value: PropTypes.any,
- name: PropTypes.string
+ values: arrayOrObjectProptype().isRequired
};
static defaultProps = {
@@ -40,16 +39,16 @@ export default class RadioButtons extends Component {
const index = Number.isNaN(parseInt(value))
? values.findIndex((val) => val.key === value)
: parseInt(value);
-
- const selectedValue = typeof value !== 'object' ? values[index] : value;
+ const selectedValue = typeof value !== 'object'
+ ? values[index]
+ : value;
const key = this.getKey(selectedValue, index);
return (
+ valueSelected={ key } >
{ this.renderContent() }
);
@@ -59,7 +58,9 @@ export default class RadioButtons extends Component {
const { values } = this.props;
return values.map((value, index) => {
- const label = typeof value === 'string' ? value : value.label || '';
+ const label = typeof value === 'string'
+ ? value
+ : value.label || '';
const description = (typeof value !== 'string' && value.description) || null;
const key = this.getKey(value, index);
@@ -67,28 +68,26 @@ export default class RadioButtons extends Component {
{ label }
{
description
- ? (
- { description }
- )
+ ? { description }
: null
}
- ) }
- />
+ }
+ value={ key } />
);
});
}
getKey (value, index) {
if (typeof value !== 'string') {
- return typeof value.key === 'undefined' ? index : value.key;
+ return typeof value.key === 'undefined'
+ ? index
+ : value.key;
}
return index;
@@ -96,8 +95,8 @@ export default class RadioButtons extends Component {
onChange = (event, index) => {
const { onChange, values } = this.props;
-
const value = values[index] || values.find((v) => v.key === index);
+
onChange(value, index);
}
}
diff --git a/js/src/util/proptypes.js b/js/src/util/proptypes.js
index 1f993768e..58b54f775 100644
--- a/js/src/util/proptypes.js
+++ b/js/src/util/proptypes.js
@@ -16,6 +16,13 @@
import { PropTypes } from 'react';
+export function arrayOrObjectProptype () {
+ return PropTypes.oneOfType([
+ PropTypes.array,
+ PropTypes.object
+ ]);
+}
+
export function nullableProptype (type) {
return PropTypes.oneOfType([
PropTypes.oneOf([ null ]),
diff --git a/js/src/util/validation.js b/js/src/util/validation.js
index 6838c2e71..a789300b4 100644
--- a/js/src/util/validation.js
+++ b/js/src/util/validation.js
@@ -35,21 +35,21 @@ export const ERRORS = {
gasBlockLimit: 'the transaction execution will exceed the block gas limit'
};
-export function validateAbi (abi, api) {
+export function validateAbi (abi) {
let abiError = null;
let abiParsed = null;
try {
abiParsed = JSON.parse(abi);
- if (!api.util.isArray(abiParsed)) {
+ if (!util.isArray(abiParsed)) {
abiError = ERRORS.invalidAbi;
return { abi, abiError, abiParsed };
}
// Validate each elements of the Array
const invalidIndex = abiParsed
- .map((o) => isValidAbiEvent(o, api) || isValidAbiFunction(o, api) || isAbiFallback(o))
+ .map((o) => isValidAbiEvent(o) || isValidAbiFunction(o) || isAbiFallback(o))
.findIndex((valid) => !valid);
if (invalidIndex !== -1) {
@@ -70,13 +70,13 @@ export function validateAbi (abi, api) {
};
}
-function isValidAbiFunction (object, api) {
+function isValidAbiFunction (object) {
if (!object) {
return false;
}
return ((object.type === 'function' && object.name) || object.type === 'constructor') &&
- (object.inputs && api.util.isArray(object.inputs));
+ (object.inputs && util.isArray(object.inputs));
}
function isAbiFallback (object) {
@@ -87,14 +87,14 @@ function isAbiFallback (object) {
return object.type === 'fallback';
}
-function isValidAbiEvent (object, api) {
+function isValidAbiEvent (object) {
if (!object) {
return false;
}
return (object.type === 'event') &&
(object.name) &&
- (object.inputs && api.util.isArray(object.inputs));
+ (object.inputs && util.isArray(object.inputs));
}
export function validateAddress (address) {