diff --git a/js/src/modals/Faucet/faucet.js b/js/src/modals/Faucet/faucet.js
new file mode 100644
index 00000000000..e4399e8ba57
--- /dev/null
+++ b/js/src/modals/Faucet/faucet.js
@@ -0,0 +1,162 @@
+// Copyright 2015-2017 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 { observer } from 'mobx-react';
+import React, { Component, PropTypes } from 'react';
+import { FormattedMessage } from 'react-intl';
+
+import { txLink } from '~/3rdparty/etherscan/links';
+import { Button, ModalBox, Portal, ShortenedHash } from '~/ui';
+import { CloseIcon, DialIcon, DoneIcon, ErrorIcon, SendIcon } from '~/ui/Icons';
+
+import Store from './store';
+
+@observer
+export default class Faucet extends Component {
+ static propTypes = {
+ address: PropTypes.string.isRequired,
+ netVersion: PropTypes.string.isRequired,
+ onClose: PropTypes.func.isRequired
+ }
+
+ store = new Store(this.props.netVersion, this.props.address);
+
+ render () {
+ const { error, isBusy, isCompleted } = this.store;
+
+ let icon = ;
+
+ if (isCompleted) {
+ icon = error
+ ?
+ : ;
+ }
+
+ return (
+
+ }
+ >
+
+
+ );
+ }
+
+ renderActions = () => {
+ const { canTransact, isBusy, isCompleted } = this.store;
+
+ return isCompleted || isBusy
+ ? (
+ }
+ key='done'
+ label={
+
+ }
+ onClick={ this.onClose }
+ />
+ )
+ : [
+ }
+ key='close'
+ label={
+
+ }
+ onClick={ this.onClose }
+ />,
+ }
+ key='request'
+ label={
+
+ }
+ onClick={ this.onExecute }
+ />
+ ];
+ }
+
+ renderSummaryDone () {
+ const { error, responseText, responseTxHash } = this.store;
+
+ return (
+
+ );
+ }
+
+ renderSummaryRequest () {
+ return (
+
+ );
+ }
+
+ onClose = () => {
+ this.props.onClose();
+ }
+
+ onExecute = () => {
+ return this.store.makeItRain();
+ }
+}
diff --git a/js/src/modals/Faucet/index.js b/js/src/modals/Faucet/index.js
new file mode 100644
index 00000000000..9aaa695dc86
--- /dev/null
+++ b/js/src/modals/Faucet/index.js
@@ -0,0 +1,17 @@
+// Copyright 2015-2017 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 .
+
+export default from './faucet';
diff --git a/js/src/modals/Faucet/store.js b/js/src/modals/Faucet/store.js
new file mode 100644
index 00000000000..356a4c080c4
--- /dev/null
+++ b/js/src/modals/Faucet/store.js
@@ -0,0 +1,126 @@
+// Copyright 2015-2017 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 apiutil from '~/api/util';
+
+const ENDPOINT = 'http://faucet.kovan.network/api/';
+
+export default class Store {
+ @observable addressReceive = null;
+ @observable addressVerified = null;
+ @observable error = null;
+ @observable responseText = null;
+ @observable responseTxHash = null;
+ @observable isBusy = false;
+ @observable isCompleted = false;
+ @observable isDestination = false;
+ @observable isDone = false;
+
+ constructor (netVersion, address) {
+ transaction(() => {
+ this.setDestination(netVersion === '42');
+
+ this.setAddressReceive(address);
+ this.setAddressVerified(address);
+ });
+ }
+
+ @computed get canTransact () {
+ return !this.isBusy && this.addressReceiveValid && this.addressVerifiedValid;
+ }
+
+ @computed get addressReceiveValid () {
+ return apiutil.isAddressValid(this.addressReceive);
+ }
+
+ @computed get addressVerifiedValid () {
+ return apiutil.isAddressValid(this.addressVerified);
+ }
+
+ @action setAddressReceive = (address) => {
+ this.addressReceive = address;
+ }
+
+ @action setAddressVerified = (address) => {
+ this.addressVerified = address;
+ }
+
+ @action setBusy = (isBusy) => {
+ this.isBusy = isBusy;
+ }
+
+ @action setCompleted = (isCompleted) => {
+ transaction(() => {
+ this.setBusy(false);
+ this.isCompleted = isCompleted;
+ });
+ }
+
+ @action setDestination = (isDestination) => {
+ this.isDestination = isDestination;
+ }
+
+ @action setError = (error) => {
+ if (error.indexOf('not certified') !== -1) {
+ this.error = `${error}. Please ensure that this account is sms certified on the mainnet.`;
+ } else {
+ this.error = error;
+ }
+ }
+
+ @action setResponse = (response) => {
+ this.responseText = response.result;
+ this.responseTxHash = response.tx;
+ }
+
+ makeItRain = () => {
+ this.setBusy(true);
+
+ const options = {
+ method: 'GET',
+ mode: 'cors'
+ };
+ const url = `${ENDPOINT}${this.addressVerified}`;
+
+ return fetch(url, options)
+ .then((response) => {
+ if (!response.ok) {
+ return null;
+ }
+
+ return response.json();
+ })
+ .catch(() => {
+ return null;
+ })
+ .then((response) => {
+ transaction(() => {
+ if (!response || response.error) {
+ this.setError(
+ response
+ ? response.error
+ : 'Unable to complete request to the faucet, the server may be unavailable. Please try again later.'
+ );
+ } else {
+ this.setResponse(response);
+ }
+
+ this.setCompleted(true);
+ });
+ });
+ }
+}
diff --git a/js/src/modals/index.js b/js/src/modals/index.js
index e64c90dce2c..6fe10bfb14e 100644
--- a/js/src/modals/index.js
+++ b/js/src/modals/index.js
@@ -24,6 +24,7 @@ export DeleteAccount from './DeleteAccount';
export DeployContract from './DeployContract';
export EditMeta from './EditMeta';
export ExecuteContract from './ExecuteContract';
+export Faucet from './Faucet';
export FirstRun from './FirstRun';
export LoadContract from './LoadContract';
export PasswordManager from './PasswordManager';
diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js
index ff3e86b2609..aae962a88d3 100644
--- a/js/src/ui/Icons/index.js
+++ b/js/src/ui/Icons/index.js
@@ -31,6 +31,7 @@ export DashboardIcon from 'material-ui/svg-icons/action/dashboard';
export DeleteIcon from 'material-ui/svg-icons/action/delete';
export DevelopIcon from 'material-ui/svg-icons/action/description';
export DoneIcon from 'material-ui/svg-icons/action/done-all';
+export DialIcon from 'material-ui/svg-icons/communication/dialpad';
export EditIcon from 'material-ui/svg-icons/content/create';
export ErrorIcon from 'material-ui/svg-icons/alert/error';
export FileUploadIcon from 'material-ui/svg-icons/file/file-upload';
diff --git a/js/src/ui/ModalBox/modalBox.js b/js/src/ui/ModalBox/modalBox.js
index f0366cf06e3..d74dc1c6ec3 100644
--- a/js/src/ui/ModalBox/modalBox.js
+++ b/js/src/ui/ModalBox/modalBox.js
@@ -22,13 +22,13 @@ import styles from './modalBox.css';
export default class ModalBox extends Component {
static propTypes = {
- children: PropTypes.node.isRequired,
+ children: PropTypes.node,
icon: PropTypes.node.isRequired,
summary: nodeOrStringProptype()
}
render () {
- const { children, icon } = this.props;
+ const { icon } = this.props;
return (