set up eslint and prettier; run prettier

This commit is contained in:
Noah Zinsmeister 2019-04-15 12:56:40 -04:00
parent da47f33b61
commit 71376cf7db
No known key found for this signature in database
GPG Key ID: 83022DD49188C9F2
65 changed files with 2975 additions and 2660 deletions

5
.prettierrc Normal file

@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 120
}

@ -42,9 +42,18 @@
"build": "react-scripts build",
"build:rinkeby": "REACT_APP_NETWORK_ID=4 REACT_APP_NETWORK='Rinkeby Test Network' yarn build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"lint:base": "yarn eslint './src/**/*.{js,jsx}'",
"format:base": "yarn prettier './src/**/*.{js,jsx,scss}'",
"lint": "yarn lint:base --fix",
"format": "yarn format:base --write",
"check:lint": "yarn lint:base",
"check:format": "yarn format:base --check",
"check:all": "yarn check:lint && yarn check:format"
},
"devDependencies": {
"prettier": "^1.17.0"
},
"devDependencies": {},
"browserslist": [
">0.2%",
"not dead",

@ -1,48 +1,44 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import c from 'classnames';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import c from 'classnames'
import QrCode from '../QrCode';
import './address-input-panel.scss';
import QrCode from '../QrCode'
import './address-input-panel.scss'
class AddressInputPanel extends Component {
static propTypes = {
title: PropTypes.string,
onChange: PropTypes.func,
value: PropTypes.string,
errorMessage: PropTypes.string,
};
errorMessage: PropTypes.string
}
static defaultProps = {
onChange() {},
value: '',
};
value: ''
}
render() {
const {
t,
title,
onChange,
value,
errorMessage,
} = this.props;
const { t, title, onChange, value, errorMessage } = this.props
return (
<div className="currency-input-panel">
<div className={c('currency-input-panel__container address-input-panel__recipient-row', {
'currency-input-panel__container--error': errorMessage,
})}>
<div
className={c('currency-input-panel__container address-input-panel__recipient-row', {
'currency-input-panel__container--error': errorMessage
})}
>
<div className="address-input-panel__input-container">
<div className="currency-input-panel__label-row">
<div className="currency-input-panel__label-container">
<span className="currency-input-panel__label">{title || t("recipientAddress")}</span>
<span className="currency-input-panel__label">{title || t('recipientAddress')}</span>
</div>
</div>
<div className="currency-input-panel__input-row">
<input
type="text"
className={c('address-input-panel__input',{
'address-input-panel__input--error': errorMessage,
className={c('address-input-panel__input', {
'address-input-panel__input--error': errorMessage
})}
placeholder="0x1234..."
onChange={e => onChange(e.target.value)}
@ -59,4 +55,4 @@ class AddressInputPanel extends Component {
}
}
export default AddressInputPanel;
export default AddressInputPanel

@ -1,4 +1,4 @@
@import "../../variables.scss";
@import '../../variables.scss';
.contextual-info {
&__summary-wrapper {

@ -1,76 +1,69 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import c from 'classnames';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import c from 'classnames'
import DropdownBlue from "../../assets/images/dropdown-blue.svg";
import DropupBlue from "../../assets/images/dropup-blue.svg";
import './contextual-info.scss';
import DropdownBlue from '../../assets/images/dropdown-blue.svg'
import DropupBlue from '../../assets/images/dropup-blue.svg'
import './contextual-info.scss'
class ContextualInfo extends Component {
static propTypes = {
openDetailsText: PropTypes.string,
renderTransactionDetails: PropTypes.func,
contextualInfo: PropTypes.string,
isError: PropTypes.bool,
};
isError: PropTypes.bool
}
static defaultProps = {
openDetailsText: 'Transaction Details',
closeDetailsText: 'Hide Details',
renderTransactionDetails() {},
contextualInfo: '',
isError: false,
};
isError: false
}
state = {
showDetails: false,
};
showDetails: false
}
renderDetails() {
if (!this.state.showDetails) {
return null;
return null
}
return (
<div className="contextual-info__details">
{this.props.renderTransactionDetails()}
</div>
);
return <div className="contextual-info__details">{this.props.renderTransactionDetails()}</div>
}
render() {
const {
openDetailsText,
closeDetailsText,
contextualInfo,
isError,
} = this.props;
const { openDetailsText, closeDetailsText, contextualInfo, isError } = this.props
if (contextualInfo) {
return (
<div className={c({ 'contextual-info--error': isError }, 'contextual-info__summary-wrapper')}>
<div>{contextualInfo}</div>
</div>
);
)
}
return [
<div
key="open-details"
className="contextual-info__summary-wrapper contextual-info__open-details-container"
onClick={() => this.setState((prevState) => {
onClick={() =>
this.setState(prevState => {
return { showDetails: !prevState.showDetails }
})}
})
}
>
{!this.state.showDetails ? (
<>
<span>{openDetailsText}</span>
<img src={DropdownBlue} alt='dropdown' />
<img src={DropdownBlue} alt="dropdown" />
</>
) : (
<>
<span>{closeDetailsText}</span>
<img src={DropupBlue} alt='dropup' />
<img src={DropupBlue} alt="dropup" />
</>
)}
</div>,
@ -79,4 +72,4 @@ class ContextualInfo extends Component {
}
}
export default ContextualInfo;
export default ContextualInfo

@ -9,20 +9,20 @@
&__container {
border-radius: 1.25rem;
box-shadow: 0 0 0 .5px $mercury-gray;
box-shadow: 0 0 0 0.5px $mercury-gray;
background-color: $white;
transition: box-shadow 200ms ease-in-out;
&--error {
box-shadow: 0 0 0 .5px $salmon-red;
box-shadow: 0 0 0 0.5px $salmon-red;
}
&:focus-within {
box-shadow: 0 0 .5px .5px $malibu-blue;
box-shadow: 0 0 0.5px 0.5px $malibu-blue;
}
&--error:focus-within {
box-shadow: 0 0 .5px .5px $salmon-red;
box-shadow: 0 0 0.5px 0.5px $salmon-red;
}
}
@ -30,9 +30,9 @@
@extend %row-nowrap;
align-items: center;
color: $dove-gray;
font-size: .75rem;
font-size: 0.75rem;
line-height: 1rem;
padding: .75rem 1rem;
padding: 0.75rem 1rem;
}
&__label-container {
@ -44,14 +44,14 @@
}
&__label-description {
opacity: .75;
margin-left: .25rem;
opacity: 0.75;
margin-left: 0.25rem;
}
&__input-row {
@extend %row-nowrap;
align-items: center;
padding: .25rem .85rem .75rem;
padding: 0.25rem 0.85rem 0.75rem;
}
&__input {
@ -62,7 +62,7 @@
}
&[type='number'] {
-moz-appearance:textfield;
-moz-appearance: textfield;
}
&::-webkit-outer-spin-button,
@ -92,14 +92,14 @@
user-select: none;
&:active {
background-color: rgba($zumthor-blue, .8);
background-color: rgba($zumthor-blue, 0.8);
}
&--selected {
background-color: $concrete-gray;
border-color: $mercury-gray;
color: $black;
padding: 0 .5rem;
padding: 0 0.5rem;
.currency-input-panel__dropdown-icon {
background-image: url(../../assets/images/dropdown.svg);
@ -128,18 +128,18 @@
user-select: none;
&--pending {
line-height: .9;
line-height: 0.9;
.loader {
height: .5rem;
width: .5rem;
height: 0.5rem;
width: 0.5rem;
}
}
}
&__dropdown-icon {
height: 1rem;
width: .75rem;
margin-left: .7rem;
width: 0.75rem;
margin-left: 0.7rem;
background-image: url(../../assets/images/dropdown-blue.svg);
background-repeat: no-repeat;
background-size: contain;
@ -147,13 +147,12 @@
}
&__selected-token-logo {
margin-right: .4rem;
margin-right: 0.4rem;
border-radius: 1rem;
object-fit: contain;
}
}
.token-modal {
background-color: $white;
position: relative;
@ -176,7 +175,7 @@
}
&__search-icon {
margin-right: .2rem;
margin-right: 0.2rem;
}
&__token-list {
@ -189,7 +188,7 @@
@extend %row-nowrap;
align-items: center;
padding: 1rem 1.5rem;
margin: .25rem .5rem;
margin: 0.25rem 0.5rem;
justify-content: space-between;
cursor: pointer;
user-select: none;
@ -235,7 +234,7 @@
justify-content: center;
background-color: $malibu-blue;
&:hover {
background-color: lighten($malibu-blue, 1)
background-color: lighten($malibu-blue, 1);
}
&:active {
@ -253,7 +252,7 @@
color: $dove-gray;
}
@media only screen and (min-width : 768px) {
@media only screen and (min-width: 768px) {
max-width: 560px;
max-height: 768px;
position: absolute;
@ -269,7 +268,6 @@
}
}
.token-modal-appear {
bottom: 0;
}

@ -1,39 +1,36 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { CSSTransitionGroup } from "react-transition-group";
import classnames from 'classnames';
import { withRouter } from 'react-router-dom';
import { withNamespaces } from 'react-i18next';
import Fuse from '../../helpers/fuse';
import Modal from '../Modal';
import TokenLogo from '../TokenLogo';
import SearchIcon from '../../assets/images/magnifying-glass.svg';
import { selectors, addPendingTx } from "../../ducks/web3connect";
import { addApprovalTx } from "../../ducks/pending";
import { addExchange } from "../../ducks/addresses";
import { BigNumber as BN } from 'bignumber.js';
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { CSSTransitionGroup } from 'react-transition-group'
import classnames from 'classnames'
import { withRouter } from 'react-router-dom'
import { withNamespaces } from 'react-i18next'
import Fuse from '../../helpers/fuse'
import Modal from '../Modal'
import TokenLogo from '../TokenLogo'
import SearchIcon from '../../assets/images/magnifying-glass.svg'
import { selectors, addPendingTx } from '../../ducks/web3connect'
import { addApprovalTx } from '../../ducks/pending'
import { addExchange } from '../../ducks/addresses'
import { BigNumber as BN } from 'bignumber.js'
import './currency-panel.scss';
import './currency-panel.scss'
import ERC20_ABI from '../../abi/erc20';
import FACTORY_ABI from '../../abi/factory';
import ERC20_ABI from '../../abi/erc20'
import FACTORY_ABI from '../../abi/factory'
const FUSE_OPTIONS = {
includeMatches: false,
threshold: 0.0,
tokenize:true,
tokenize: true,
location: 0,
distance: 100,
maxPatternLength: 45,
minMatchCharLength: 1,
keys: [
{name:"address",weight:0.8},
{name:"label",weight:0.5},
]
};
keys: [{ name: 'address', weight: 0.8 }, { name: 'label', weight: 0.5 }]
}
const TOKEN_ADDRESS_TO_LABEL = { ETH: 'ETH' };
const TOKEN_ADDRESS_TO_LABEL = { ETH: 'ETH' }
class CurrencyInputPanel extends Component {
static propTypes = {
@ -44,10 +41,10 @@ class CurrencyInputPanel extends Component {
onCurrencySelected: PropTypes.func,
onValueChange: PropTypes.func,
tokenAddresses: PropTypes.shape({
addresses: PropTypes.array.isRequired,
addresses: PropTypes.array.isRequired
}).isRequired,
exchangeAddresses: PropTypes.shape({
fromToken: PropTypes.object.isRequired,
fromToken: PropTypes.object.isRequired
}).isRequired,
factoryAddress: PropTypes.string,
selectedTokens: PropTypes.array.isRequired,
@ -59,52 +56,52 @@ class CurrencyInputPanel extends Component {
addExchange: PropTypes.func.isRequired,
filteredTokens: PropTypes.arrayOf(PropTypes.string),
disableUnlock: PropTypes.bool,
renderInput: PropTypes.func,
};
renderInput: PropTypes.func
}
static defaultProps = {
selectedTokens: [],
filteredTokens: [],
onCurrencySelected() {},
onValueChange() {},
selectedTokenAddress: '',
};
selectedTokenAddress: ''
}
state = {
isShowingModal: false,
searchQuery: '',
loadingExchange: false,
};
createTokenList = () => {
const { filteredTokens } = this.props;
let tokens = this.props.tokenAddresses.addresses;
let tokenList = [ { value: 'ETH', label: 'ETH', address: 'ETH' } ];
for (let i = 0; i < tokens.length; i++) {
let entry = { value: '', label: '' };
entry.value = tokens[i][0];
entry.label = tokens[i][0];
entry.address = tokens[i][1];
tokenList.push(entry);
TOKEN_ADDRESS_TO_LABEL[tokens[i][1]] = tokens[i][0];
loadingExchange: false
}
return tokenList.filter(({ address }) => !filteredTokens.includes(address));
};
createTokenList = () => {
const { filteredTokens } = this.props
let tokens = this.props.tokenAddresses.addresses
let tokenList = [{ value: 'ETH', label: 'ETH', address: 'ETH' }]
onTokenSelect = (address) => {
for (let i = 0; i < tokens.length; i++) {
let entry = { value: '', label: '' }
entry.value = tokens[i][0]
entry.label = tokens[i][0]
entry.address = tokens[i][1]
tokenList.push(entry)
TOKEN_ADDRESS_TO_LABEL[tokens[i][1]] = tokens[i][0]
}
return tokenList.filter(({ address }) => !filteredTokens.includes(address))
}
onTokenSelect = address => {
this.setState({
searchQuery: '',
isShowingModal: false,
});
isShowingModal: false
})
this.props.onCurrencySelected(address);
};
this.props.onCurrencySelected(address)
}
renderTokenList() {
const tokens = this.createTokenList();
const { loadingExchange, searchQuery } = this.state;
const tokens = this.createTokenList()
const { loadingExchange, searchQuery } = this.state
const {
t,
selectedTokens,
@ -115,8 +112,8 @@ class CurrencyInputPanel extends Component {
factoryAddress,
exchangeAddresses: { fromToken },
addExchange,
history,
} = this.props;
history
} = this.props
if (loadingExchange) {
return (
@ -124,52 +121,52 @@ class CurrencyInputPanel extends Component {
<div className="loader" />
<div>Searching for Exchange...</div>
</div>
);
)
}
if (web3 && web3.utils && web3.utils.isAddress(searchQuery)) {
const tokenAddress = searchQuery;
const { label } = selectors().getBalance(account, tokenAddress);
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress);
const exchangeAddress = fromToken[tokenAddress];
const tokenAddress = searchQuery
const { label } = selectors().getBalance(account, tokenAddress)
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress)
const exchangeAddress = fromToken[tokenAddress]
if (!exchangeAddress) {
this.setState({loadingExchange: true});
this.setState({ loadingExchange: true })
factory.methods.getExchange(tokenAddress).call((err, data) => {
if (!err && data !== '0x0000000000000000000000000000000000000000') {
addExchange({ label, tokenAddress, exchangeAddress: data });
addExchange({ label, tokenAddress, exchangeAddress: data })
}
this.setState({loadingExchange: false});
});
return;
this.setState({ loadingExchange: false })
})
return
}
}
if (disableTokenSelect) {
return;
return
}
let results;
let results
if (!searchQuery) {
results = tokens;
results = tokens
} else {
const fuse = new Fuse(tokens, FUSE_OPTIONS);
results = fuse.search(this.state.searchQuery);
const fuse = new Fuse(tokens, FUSE_OPTIONS)
results = fuse.search(this.state.searchQuery)
}
if (!results.length && web3 && web3.utils && web3.utils.isAddress(searchQuery)) {
const { label } = selectors().getBalance(account, searchQuery);
const { label } = selectors().getBalance(account, searchQuery)
return [
<div key="token-modal-no-exchange" className="token-modal__token-row token-modal__token-row--no-exchange">
<div>{t("noExchange")}</div>
<div>{t('noExchange')}</div>
</div>,
<div
key="token-modal-create-exchange"
className="token-modal__token-row token-modal__token-row--create-exchange"
onClick={() => {
this.setState({ isShowingModal: false });
history.push(`/create-exchange/${searchQuery}`);
this.setState({ isShowingModal: false })
history.push(`/create-exchange/${searchQuery}`)
}}
>
<div>{`Create exchange for ${label}`}</div>
@ -180,34 +177,32 @@ class CurrencyInputPanel extends Component {
if (!results.length) {
return (
<div className="token-modal__token-row token-modal__token-row--no-exchange">
<div>{t("noExchange")}</div>
<div>{t('noExchange')}</div>
</div>
)
}
return results.map(({ label, address }) => {
const isSelected = selectedTokens.indexOf(address) > -1;
const isSelected = selectedTokens.indexOf(address) > -1
return (
<div
key={label}
className={
classnames('token-modal__token-row', {
'token-modal__token-row--selected': isSelected,
})
}
className={classnames('token-modal__token-row', {
'token-modal__token-row--selected': isSelected
})}
onClick={() => this.onTokenSelect(address)}
>
<TokenLogo className="token-modal__token-logo" address={address} />
<div className="token-modal__token-label" >{label}</div>
<div className="token-modal__token-label">{label}</div>
</div>
);
});
)
})
}
renderModal() {
if (!this.state.isShowingModal) {
return null;
return null
}
return (
@ -224,21 +219,19 @@ class CurrencyInputPanel extends Component {
<div className="token-modal__search-container">
<input
type="text"
placeholder={this.props.t("searchOrPaste")}
placeholder={this.props.t('searchOrPaste')}
className="token-modal__search-input"
onChange={e => {
this.setState({ searchQuery: e.target.value });
this.setState({ searchQuery: e.target.value })
}}
/>
<img src={SearchIcon} className="token-modal__search-icon" alt='search' />
</div>
<div className="token-modal__token-list">
{this.renderTokenList()}
<img src={SearchIcon} className="token-modal__search-icon" alt="search" />
</div>
<div className="token-modal__token-list">{this.renderTokenList()}</div>
</div>
</CSSTransitionGroup>
</Modal>
);
)
}
renderUnlockButton() {
@ -254,69 +247,58 @@ class CurrencyInputPanel extends Component {
pendingApprovals,
value,
addApprovalTx,
addPendingTx,
} = this.props;
addPendingTx
} = this.props
if (disableUnlock || !selectedTokenAddress || selectedTokenAddress === 'ETH') {
return;
return
}
const { value: allowance, decimals, label } = selectors().getApprovals(selectedTokenAddress, account, fromToken[selectedTokenAddress]);
if (
!label ||
(
allowance.isGreaterThanOrEqualTo(BN((value || 0) * 10 ** decimals)) &&
!BN(allowance).isZero()
const { value: allowance, decimals, label } = selectors().getApprovals(
selectedTokenAddress,
account,
fromToken[selectedTokenAddress]
)
) {
return;
if (!label || (allowance.isGreaterThanOrEqualTo(BN((value || 0) * 10 ** decimals)) && !BN(allowance).isZero())) {
return
}
const approvalTxId = pendingApprovals[selectedTokenAddress];
const approvalTxId = pendingApprovals[selectedTokenAddress]
if (approvalTxId && transactions.pending.includes(approvalTxId)) {
return (
<button
className='currency-input-panel__sub-currency-select currency-input-panel__sub-currency-select--pending'
>
<button className="currency-input-panel__sub-currency-select currency-input-panel__sub-currency-select--pending">
<div className="loader" />
{t("pending")}
{t('pending')}
</button>
);
)
}
return (
<button
className='currency-input-panel__sub-currency-select'
className="currency-input-panel__sub-currency-select"
onClick={() => {
const contract = new web3.eth.Contract(ERC20_ABI, selectedTokenAddress);
const amount = BN(10 ** decimals).multipliedBy(10 ** 8).toFixed(0);
contract.methods.approve(fromToken[selectedTokenAddress], amount)
.send({ from: account }, (err, data) => {
const contract = new web3.eth.Contract(ERC20_ABI, selectedTokenAddress)
const amount = BN(10 ** decimals)
.multipliedBy(10 ** 8)
.toFixed(0)
contract.methods.approve(fromToken[selectedTokenAddress], amount).send({ from: account }, (err, data) => {
if (!err && data) {
addPendingTx(data);
addApprovalTx({ tokenAddress: selectedTokenAddress, txId: data});
addPendingTx(data)
addApprovalTx({ tokenAddress: selectedTokenAddress, txId: data })
}
});
})
}}
>
{t("unlock")}
{t('unlock')}
</button>
);
)
}
renderInput() {
const {
t,
errorMessage,
value,
onValueChange,
selectedTokenAddress,
disableTokenSelect,
renderInput,
} = this.props;
const { t, errorMessage, value, onValueChange, selectedTokenAddress, disableTokenSelect, renderInput } = this.props
if (typeof renderInput === 'function') {
return renderInput();
return renderInput()
}
return (
@ -324,72 +306,64 @@ class CurrencyInputPanel extends Component {
<input
type="number"
min="0"
className={classnames('currency-input-panel__input',{
'currency-input-panel__input--error': errorMessage,
className={classnames('currency-input-panel__input', {
'currency-input-panel__input--error': errorMessage
})}
placeholder="0.0"
onChange={e => onValueChange(e.target.value)}
onKeyPress={e => {
const charCode = e.which ? e.which : e.keyCode;
const charCode = e.which ? e.which : e.keyCode
// Prevent 'minus' character
if (charCode === 45) {
e.preventDefault();
e.stopPropagation();
e.preventDefault()
e.stopPropagation()
}
}}
value={value}
/>
{ this.renderUnlockButton() }
{this.renderUnlockButton()}
<button
className={classnames("currency-input-panel__currency-select", {
className={classnames('currency-input-panel__currency-select', {
'currency-input-panel__currency-select--selected': selectedTokenAddress,
'currency-input-panel__currency-select--disabled': disableTokenSelect,
'currency-input-panel__currency-select--disabled': disableTokenSelect
})}
onClick={() => {
if (!disableTokenSelect) {
this.setState({ isShowingModal: true });
this.setState({ isShowingModal: true })
}
}}
>
{
selectedTokenAddress
? (
<TokenLogo
className="currency-input-panel__selected-token-logo"
address={selectedTokenAddress}
/>
)
: null
}
{ TOKEN_ADDRESS_TO_LABEL[selectedTokenAddress] || t("selectToken") }
{selectedTokenAddress ? (
<TokenLogo className="currency-input-panel__selected-token-logo" address={selectedTokenAddress} />
) : null}
{TOKEN_ADDRESS_TO_LABEL[selectedTokenAddress] || t('selectToken')}
<span className="currency-input-panel__dropdown-icon" />
</button>
</div>
);
)
}
render() {
const {
title,
description,
extraText,
errorMessage,
} = this.props;
const { title, description, extraText, errorMessage } = this.props
return (
<div className="currency-input-panel">
<div className={classnames('currency-input-panel__container', {
'currency-input-panel__container--error': errorMessage,
})}>
<div
className={classnames('currency-input-panel__container', {
'currency-input-panel__container--error': errorMessage
})}
>
<div className="currency-input-panel__label-row">
<div className="currency-input-panel__label-container">
<span className="currency-input-panel__label">{title}</span>
<span className="currency-input-panel__label-description">{description}</span>
</div>
<span className={classnames('currency-input-panel__extra-text', {
'currency-input-panel__extra-text--error': errorMessage,
})}>
<span
className={classnames('currency-input-panel__extra-text', {
'currency-input-panel__extra-text--error': errorMessage
})}
>
{extraText}
</span>
</div>
@ -412,13 +386,13 @@ export default withRouter(
approvals: state.web3connect.approvals,
transactions: state.web3connect.transactions,
web3: state.web3connect.web3,
pendingApprovals: state.pending.approvals,
pendingApprovals: state.pending.approvals
}),
dispatch => ({
selectors: () => dispatch(selectors()),
addExchange: opts => dispatch(addExchange(opts)),
addPendingTx: opts => dispatch(addPendingTx(opts)),
addApprovalTx: opts => dispatch(addApprovalTx(opts)),
}),
addApprovalTx: opts => dispatch(addApprovalTx(opts))
})
)(withNamespaces()(CurrencyInputPanel))
);
)

@ -1,11 +1,11 @@
@import "../../variables.scss";
@import '../../variables.scss';
.header {
@extend %col-nowrap;
&__top {
@extend %row-nowrap;
padding: 1.25rem .75rem;
padding: 1.25rem 0.75rem;
align-items: center;
border-bottom: 1px solid $concrete-gray;
}
@ -28,22 +28,22 @@
}
&--inactive {
opacity: .5;
opacity: 0.5;
}
&__dialog {
@extend %col-nowrap;
border-radius: .875rem;
border-radius: 0.875rem;
border: 1px solid $mercury-gray;
margin: 1rem .75rem 0 .75rem;
margin: 1rem 0.75rem 0 0.75rem;
padding: 1.5rem 1rem;
text-align: center;
display: none;
&__description {
font-size: .75rem;
font-size: 0.75rem;
color: $dove-gray;
margin-top: .4rem;
margin-top: 0.4rem;
}
&--disconnected {
@ -68,7 +68,7 @@
}
}
@media only screen and (min-width : 768px) {
@media only screen and (min-width: 768px) {
//position: fixed;
top: 0px;
left: 0px;

@ -1,17 +1,17 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import UAParser from 'ua-parser-js';
import { withNamespaces } from 'react-i18next';
import Logo from '../Logo';
import CoinbaseWalletLogo from '../../assets/images/coinbase-wallet-logo.png';
import TrustLogo from '../../assets/images/trust-wallet-logo.svg';
import BraveLogo from '../../assets/images/brave-logo.svg';
import MetamaskLogo from '../../assets/images/metamask-logo.svg';
import Web3Status from '../Web3Status';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import classnames from 'classnames'
import UAParser from 'ua-parser-js'
import { withNamespaces } from 'react-i18next'
import Logo from '../Logo'
import CoinbaseWalletLogo from '../../assets/images/coinbase-wallet-logo.png'
import TrustLogo from '../../assets/images/trust-wallet-logo.svg'
import BraveLogo from '../../assets/images/brave-logo.svg'
import MetamaskLogo from '../../assets/images/metamask-logo.svg'
import Web3Status from '../Web3Status'
import "./header.scss";
import './header.scss'
const links = {
coinbaseWallet: {
@ -19,135 +19,132 @@ const links = {
ios: 'https://itunes.apple.com/us/app/coinbase-wallet/id1278383455'
},
trust: {
android: 'https://links.trustwalletapp.com/a/key_live_lfvIpVeI9TFWxPCqwU8rZnogFqhnzs4D?&event=openURL&url=https://uniswap.exchange/swap',
ios: 'https://links.trustwalletapp.com/a/key_live_lfvIpVeI9TFWxPCqwU8rZnogFqhnzs4D?&event=openURL&url=https://uniswap.exchange/swap',
android:
'https://links.trustwalletapp.com/a/key_live_lfvIpVeI9TFWxPCqwU8rZnogFqhnzs4D?&event=openURL&url=https://uniswap.exchange/swap',
ios:
'https://links.trustwalletapp.com/a/key_live_lfvIpVeI9TFWxPCqwU8rZnogFqhnzs4D?&event=openURL&url=https://uniswap.exchange/swap'
},
metamask: {
chrome: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
chrome: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn'
},
brave: {
android: 'https://play.google.com/store/apps/details?id=com.brave.browser',
ios: 'https://itunes.apple.com/us/app/brave-browser-fast-adblocker/id1052879175',
},
};
ios: 'https://itunes.apple.com/us/app/brave-browser-fast-adblocker/id1052879175'
}
}
const ua = new UAParser(window.navigator.userAgent);
const ua = new UAParser(window.navigator.userAgent)
function getTrustLink() {
const os = ua.getOS();
const os = ua.getOS()
if (os.name === 'Android') {
return links.trust.android;
return links.trust.android
}
if (os.name === 'iOS') {
return links.trust.ios;
return links.trust.ios
}
}
function getCoinbaseWalletLink() {
const os = ua.getOS();
const os = ua.getOS()
if (os.name === 'Android') {
return links.coinbaseWallet.android;
return links.coinbaseWallet.android
}
if (os.name === 'iOS') {
return links.coinbaseWallet.ios;
return links.coinbaseWallet.ios
}
}
function getBraveLink() {
const os = ua.getOS();
const os = ua.getOS()
if (os.name === 'Mac OS') {
return links.brave.ios;
return links.brave.ios
}
return links.brave.android;
return links.brave.android
}
function getMetamaskLink() {
return links.metamask.chrome;
return links.metamask.chrome
}
function isMobile() {
return ua.getDevice().type === 'mobile';
return ua.getDevice().type === 'mobile'
}
class BlockingWarning extends Component {
render () {
const {
t,
isConnected,
initialized,
networkId,
} = this.props;
let content = [];
render() {
const { t, isConnected, initialized, networkId } = this.props
let content = []
const correctNetworkId = process.env.REACT_APP_NETWORK_ID || 1;
const correctNetwork = process.env.REACT_APP_NETWORK || 'Main Ethereum Network';
const correctNetworkId = process.env.REACT_APP_NETWORK_ID || 1
const correctNetwork = process.env.REACT_APP_NETWORK || 'Main Ethereum Network'
const wrongNetwork = networkId !== correctNetworkId;
const wrongNetwork = networkId !== correctNetworkId
if (wrongNetwork && initialized) {
content = [
<div key="warning-title">{t("wrongNetwork")}</div>,
<div key="warning-title">{t('wrongNetwork')}</div>,
<div key="warning-desc" className="header__dialog__description">
{t("switchNetwork", {correctNetwork})}
</div>,
];
{t('switchNetwork', { correctNetwork })}
</div>
]
}
if (!isConnected && initialized) {
content = [
<div key="warning-title">{t("noWallet")}</div>,
<div key="warning-title">{t('noWallet')}</div>,
<div key="warning-desc" className="header__dialog__description">
{
isMobile()
? t("installWeb3MobileBrowser")
: t("installMetamask")
}
{isMobile() ? t('installWeb3MobileBrowser') : t('installMetamask')}
</div>,
<div key="warning-logos" className="header__download">
{
isMobile()
? (
[
<img alt='coinbase' src={CoinbaseWalletLogo} key="coinbase-wallet" onClick={() => window.open(getCoinbaseWalletLink(), '_blank')} />,
<img alt='trust' src={TrustLogo} key="trust" onClick={() => window.open(getTrustLink(), '_blank')} />
{isMobile()
? [
<img
alt="coinbase"
src={CoinbaseWalletLogo}
key="coinbase-wallet"
onClick={() => window.open(getCoinbaseWalletLink(), '_blank')}
/>,
<img alt="trust" src={TrustLogo} key="trust" onClick={() => window.open(getTrustLink(), '_blank')} />
]
)
: (
[
<img alt='metamask' src={MetamaskLogo} key="metamask" onClick={() => window.open(getMetamaskLink(), '_blank')} />,
<img alt='brave' src={BraveLogo} key="brave" onClick={() => window.open(getBraveLink(), '_blank')} />
: [
<img
alt="metamask"
src={MetamaskLogo}
key="metamask"
onClick={() => window.open(getMetamaskLink(), '_blank')}
/>,
<img alt="brave" src={BraveLogo} key="brave" onClick={() => window.open(getBraveLink(), '_blank')} />
]}
</div>
]
)
}
</div>,
];
}
return (
<div
className={classnames('header__dialog', {
'header__dialog--disconnected': (!isConnected || wrongNetwork) && initialized,
'header__dialog--disconnected': (!isConnected || wrongNetwork) && initialized
})}
>
{content}
</div>
);
)
}
}
function Header (props) {
function Header(props) {
return (
<div className="header">
<BlockingWarning {...props} />
<div
className={classnames('header__top', {
'header--inactive': !props.isConnected,
'header--inactive': !props.isConnected
})}
>
<Logo />
@ -162,15 +159,13 @@ function Header (props) {
Header.propTypes = {
currentAddress: PropTypes.string,
isConnected: PropTypes.bool.isRequired,
};
isConnected: PropTypes.bool.isRequired
}
export default connect(
state => ({
export default connect(state => ({
currentAddress: state.web3connect.account,
initialized: state.web3connect.initialized,
isConnected: !!state.web3connect.account,
web3: state.web3connect.web3,
networkId: state.web3connect.networkId,
}),
)(withNamespaces()(Header));
networkId: state.web3connect.networkId
}))(withNamespaces()(Header))

@ -1,10 +1,12 @@
import React from 'react';
import "./logo.scss";
import React from 'react'
import './logo.scss'
export default function Logo(props) {
return (
<div className="logo">
<span role="img" aria-label="logo">🦄</span>
<span role="img" aria-label="logo">
🦄
</span>
</div>
);
)
}

@ -1,15 +1,15 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import { CSSTransitionGroup } from 'react-transition-group';
import './modal.scss';
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
import { CSSTransitionGroup } from 'react-transition-group'
import './modal.scss'
const modalRoot = document.querySelector('#modal-root');
const modalRoot = document.querySelector('#modal-root')
export default class Modal extends Component {
static propTypes = {
onClose: PropTypes.func.isRequired,
};
onClose: PropTypes.func.isRequired
}
componentDidMount() {
// The portal element is inserted in the DOM tree after
@ -28,7 +28,7 @@ export default class Modal extends Component {
setTimeout(() => {
// modalRoot.style.display = 'none';
// modalRoot.removeChild(this.el);
}, 500);
}, 500)
}
render() {
@ -46,7 +46,7 @@ export default class Modal extends Component {
</CSSTransitionGroup>
{this.props.children}
</div>,
modalRoot,
);
modalRoot
)
}
}

@ -4,9 +4,8 @@
position: relative;
height: 100vh;
width: 100vw;
background-color: rgba($black, .6);
background-color: rgba($black, 0.6);
z-index: 1000;
}
.modal-container-appear {

@ -1,23 +1,23 @@
@import "../../variables";
@import '../../variables';
.beta-message {
@extend %row-nowrap;
flex: 1 0 auto;
align-items: center;
position: relative;
padding: .5rem 1rem;
padding: 0.5rem 1rem;
margin-bottom: 1rem;
border: 1px solid rgba($wisteria-purple, .4);
background-color: rgba($wisteria-purple, .1);
border: 1px solid rgba($wisteria-purple, 0.4);
background-color: rgba($wisteria-purple, 0.1);
border-radius: 2rem;
font-size: .75rem;
font-size: 0.75rem;
line-height: 1rem;
text-align: center;
color: $wisteria-purple;
&:after {
content: '';
top: .5rem;
top: 0.5rem;
right: 1rem;
position: absolute;
color: $wisteria-purple;

@ -1,71 +1,66 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import { dismissBetaMessage } from '../../ducks/app';
import {Tab, Tabs} from "../Tab";
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { withNamespaces } from 'react-i18next'
import { dismissBetaMessage } from '../../ducks/app'
import { Tab, Tabs } from '../Tab'
import './beta-message.scss';
import './beta-message.scss'
class NavigationTabs extends Component {
static propTypes = {
history: PropTypes.shape({
push: PropTypes.func.isRequired,
push: PropTypes.func.isRequired
}),
className: PropTypes.string,
dismissBetaMessage: PropTypes.func.isRequired,
showBetaMessage: PropTypes.bool.isRequired,
};
showBetaMessage: PropTypes.bool.isRequired
}
constructor(props) {
super(props);
super(props)
this.state = {
selectedPath: this.props.location.pathname,
className: '',
showWarning: true,
};
showWarning: true
}
}
renderTab(name, path, regex) {
const { push } = this.props.history;
return (
<Tab
text={name}
onClick={() => push(path)}
isSelected={regex.test(this.props.location.pathname)}
/>
)
const { push } = this.props.history
return <Tab text={name} onClick={() => push(path)} isSelected={regex.test(this.props.location.pathname)} />
}
render() {
const { t, showBetaMessage, className, dismissBetaMessage } = this.props;
const { t, showBetaMessage, className, dismissBetaMessage } = this.props
return (
<div>
<Tabs className={className}>
{ this.renderTab(t("swap"), '/swap', /swap/) }
{ this.renderTab(t("send"), '/send', /send/) }
{ this.renderTab(t("pool"), '/add-liquidity', /add-liquidity|remove-liquidity|create-exchange/) }
{this.renderTab(t('swap'), '/swap', /swap/)}
{this.renderTab(t('send'), '/send', /send/)}
{this.renderTab(t('pool'), '/add-liquidity', /add-liquidity|remove-liquidity|create-exchange/)}
</Tabs>
{
showBetaMessage && (
{showBetaMessage && (
<div className="beta-message" onClick={dismissBetaMessage}>
<span role='img' aria-label='warning'>💀</span> {t("betaWarning")}
<span role="img" aria-label="warning">
💀
</span>{' '}
{t('betaWarning')}
</div>
)}
</div>
)
}
</div>
);
}
}
export default withRouter(
connect(
state => ({
showBetaMessage: state.app.showBetaMessage,
showBetaMessage: state.app.showBetaMessage
}),
dispatch => ({
dismissBetaMessage: () => dispatch(dismissBetaMessage()),
}),
dismissBetaMessage: () => dispatch(dismissBetaMessage())
})
)(withNamespaces()(NavigationTabs))
);
)

@ -1,13 +1,13 @@
import React from 'react';
import React from 'react'
import './oversized-panel.scss';
import './oversized-panel.scss'
export default function OversizedPanel(props) {
return (
<div className="oversized-panel">
{ props.hideTop || <div className="oversized-panel__top" /> }
{props.hideTop || <div className="oversized-panel__top" />}
{props.children}
{ props.hideBottom || <div className="oversized-panel__bottom" /> }
{props.hideBottom || <div className="oversized-panel__bottom" />}
</div>
);
)
}

@ -5,12 +5,12 @@
background-color: $concrete-gray;
width: calc(100% - 1rem);
margin: 0 auto;
border-radius: .625rem;
border-radius: 0.625rem;
&__top {
content: "";
content: '';
position: absolute;
top: -.5rem;
top: -0.5rem;
left: 0;
height: 1rem;
width: 100%;

@ -1,70 +1,71 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { CSSTransitionGroup } from "react-transition-group";
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { CSSTransitionGroup } from 'react-transition-group'
import Modal from '../Modal';
import QrCodeSVG from '../../assets/images/qr-code.svg';
import QrScanner from '../../libraries/qr-scanner';
import './qr-code.scss';
import Modal from '../Modal'
import QrCodeSVG from '../../assets/images/qr-code.svg'
import QrScanner from '../../libraries/qr-scanner'
import './qr-code.scss'
class QrCode extends Component {
static propTypes = {
onValueReceived: PropTypes.func,
};
onValueReceived: PropTypes.func
}
static defaultProps = {
onValueReceived() {},
};
onValueReceived() {}
}
state = {
videoOpen: false,
stream: null,
};
stream: null
}
componentDidUpdate() {
const { videoOpen, stream } = this.state;
const { videoOpen, stream } = this.state
if (videoOpen && !stream && this.videoRef) {
this.startStreamingVideo(this.videoRef)
} else if (!videoOpen && stream) {
this.setState({stream: null});
this.setState({ stream: null })
}
}
startStreamingVideo(videoRef) {
if (navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: { facingMode: 'user'}, audio: false})
.then((stream) => {
videoRef.srcObject = stream;
new QrScanner(videoRef, (val) => {
this.closeVideo();
this.props.onValueReceived(val);
navigator.mediaDevices
.getUserMedia({ video: { facingMode: 'user' }, audio: false })
.then(stream => {
videoRef.srcObject = stream
new QrScanner(videoRef, val => {
this.closeVideo()
this.props.onValueReceived(val)
})
this.setState({
stream: stream.getTracks()[0]
});
})
.catch((error) => {
this.closeVideo();
console.error(error);
});
})
.catch(error => {
this.closeVideo()
console.error(error)
})
}
}
openVideo = () => {
this.setState({videoOpen: true});
this.setState({ videoOpen: true })
}
closeVideo = () => {
if (this.state.stream) {
this.state.stream.stop();
this.state.stream.stop()
}
this.setState({ videoOpen: false, stream: null })
this.videoRef = null
}
this.setState({videoOpen: false, stream: null});
this.videoRef = null;
};
setVideoRef = (element) => {
this.videoRef = element;
setVideoRef = element => {
this.videoRef = element
}
renderQrReader() {
@ -80,31 +81,29 @@ class QrCode extends Component {
transitionEnterTimeout={200}
>
<div className="qr-code__modal">
<video
playsInline
muted
autoPlay
ref={this.setVideoRef}
className="qr-code__video"
>
</video>
<video playsInline muted autoPlay ref={this.setVideoRef} className="qr-code__video" />
</div>
</CSSTransitionGroup>
</Modal>
);
)
}
return null;
return null
}
render() {
return [
<img key="icon" src={QrCodeSVG} alt='code' onClick={() => {
this.state.videoOpen ? this.closeVideo() : this.openVideo();
}} />,
<img
key="icon"
src={QrCodeSVG}
alt="code"
onClick={() => {
this.state.videoOpen ? this.closeVideo() : this.openVideo()
}}
/>,
this.renderQrReader()
]
}
}
export default QrCode;
export default QrCode

@ -1,4 +1,3 @@
.qr-code {
&__video {
height: 100%;

@ -1,38 +1,33 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import './tab.scss';
import './tab.scss'
export const Tabs = props => {
return (
<div className={classnames("tabs", props.className)}>
{ props.children }
</div>
);
};
return <div className={classnames('tabs', props.className)}>{props.children}</div>
}
export const Tab = props => {
return (
<div
className={classnames("tab", {
'tab--selected': props.isSelected,
className={classnames('tab', {
'tab--selected': props.isSelected
})}
onClick={props.onClick}
>
{ props.text ? <span>{props.text}</span> : null }
{props.text ? <span>{props.text}</span> : null}
</div>
);
};
)
}
Tab.propTypes = {
className: PropTypes.string,
text: PropTypes.string,
isSelected: PropTypes.bool,
onClick: PropTypes.func,
};
onClick: PropTypes.func
}
Tab.defaultProps = {
className: '',
};
className: ''
}

@ -6,7 +6,7 @@
height: 2.5rem;
background-color: $concrete-gray;
border-radius: 3rem;
box-shadow: 0 0 0 .5px darken($concrete-gray, 5);
box-shadow: 0 0 0 0.5px darken($concrete-gray, 5);
.tab:first-child {
//margin-left: -1px;
@ -35,7 +35,7 @@
&--selected {
background-color: $white;
border-radius: 3rem;
box-shadow: 0 0 .5px .5px $mercury-gray;
box-shadow: 0 0 0.5px 0.5px $mercury-gray;
font-weight: 500;
span {

@ -1,66 +1,72 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import EthereumLogo from '../../assets/images/ethereum-logo.svg';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import EthereumLogo from '../../assets/images/ethereum-logo.svg'
const RINKEBY_TOKEN_MAP = {
'0xDA5B056Cfb861282B4b59d29c9B395bcC238D29B': '0x0d8775f648430679a709e98d2b0cb6250d2887ef',
'0x2448eE2641d78CC42D7AD76498917359D961A783': '0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359',
'0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85': '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2',
'0x879884c3C46A24f56089f3bBbe4d5e38dB5788C0': '0xd26114cd6ee289accf82350c8d8487fedb8a0c07',
'0xF22e3F33768354c9805d046af3C0926f27741B43': '0xe41d2489571d322189246dafa5ebde1f4699f498',
};
'0xF22e3F33768354c9805d046af3C0926f27741B43': '0xe41d2489571d322189246dafa5ebde1f4699f498'
}
const TOKEN_ICON_API = 'https://raw.githubusercontent.com/TrustWallet/tokens/master/tokens';
const BAD_IMAGES = {};
const TOKEN_ICON_API = 'https://raw.githubusercontent.com/TrustWallet/tokens/master/tokens'
const BAD_IMAGES = {}
export default class TokenLogo extends Component {
static propTypes = {
address: PropTypes.string,
size: PropTypes.string,
className: PropTypes.string,
};
className: PropTypes.string
}
static defaultProps = {
address: '',
size: '1rem',
className: '',
};
className: ''
}
state = {
error: false,
};
error: false
}
render() {
const { address, size, className } = this.props;
const { address, size, className } = this.props
// let path = GenericTokenLogo;
let path = '';
const mainAddress = RINKEBY_TOKEN_MAP[address] ? RINKEBY_TOKEN_MAP[address] : address;
let path = ''
const mainAddress = RINKEBY_TOKEN_MAP[address] ? RINKEBY_TOKEN_MAP[address] : address
if (mainAddress === 'ETH') {
path = EthereumLogo;
path = EthereumLogo
}
if (!this.state.error && !BAD_IMAGES[mainAddress] && mainAddress !== 'ETH') {
path = `${TOKEN_ICON_API}/${mainAddress.toLowerCase()}.png`;
path = `${TOKEN_ICON_API}/${mainAddress.toLowerCase()}.png`
}
if (!path) {
return <div className={className} style={{ width: size, fontSize: size }}><span role='img' aria-label='thinking'>🤔</span></div>
return (
<div className={className} style={{ width: size, fontSize: size }}>
<span role="img" aria-label="thinking">
🤔
</span>
</div>
)
}
return (
<img
alt='images'
alt="images"
src={path}
className={className}
style={{
width: size,
height: size,
height: size
}}
onError={() => {
this.setState({ error: true });
BAD_IMAGES[mainAddress] = true;
this.setState({ error: true })
BAD_IMAGES[mainAddress] = true
}}
/>
);
)
}
}

@ -1,53 +1,51 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import Jazzicon from 'jazzicon';
import { CSSTransitionGroup } from "react-transition-group";
import { withNamespaces } from 'react-i18next';
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import classnames from 'classnames'
import Jazzicon from 'jazzicon'
import { CSSTransitionGroup } from 'react-transition-group'
import { withNamespaces } from 'react-i18next'
import { ethers } from 'ethers'
import './web3-status.scss';
import Modal from '../Modal';
import './web3-status.scss'
import Modal from '../Modal'
function getEtherscanLink(tx) {
return `https://etherscan.io/tx/${tx}`;
return `https://etherscan.io/tx/${tx}`
}
console.log(ethers)
class Web3Status extends Component {
state = {
isShowingModal: false,
};
isShowingModal: false
}
handleClick = () => {
if (this.props.pending.length && !this.state.isShowingModal) {
this.setState({isShowingModal: true});
this.setState({ isShowingModal: true })
}
}
};
renderPendingTransactions() {
return this.props.pending.map((transaction) => {
return this.props.pending.map(transaction => {
return (
<div
key={transaction}
className={classnames('pending-modal__transaction-row')}
onClick={() => window.open(getEtherscanLink(transaction), '_blank')}
>
<div className="pending-modal__transaction-label">
{transaction}
</div>
<div className="pending-modal__transaction-label">{transaction}</div>
<div className="pending-modal__pending-indicator">
<div className="loader" /> {this.props.t("pending")}
<div className="loader" /> {this.props.t('pending')}
</div>
</div>
);
});
)
})
}
renderModal() {
if (!this.state.isShowingModal) {
return null;
return null
}
return (
@ -68,84 +66,82 @@ class Web3Status extends Component {
</div>
</CSSTransitionGroup>
</Modal>
);
)
}
render() {
const { t, address, pending, confirmed } = this.props;
const hasPendingTransactions = !!pending.length;
const hasConfirmedTransactions = !!confirmed.length;
const { t, address, pending, confirmed } = this.props
const hasPendingTransactions = !!pending.length
const hasConfirmedTransactions = !!confirmed.length
return (
<div
className={classnames("web3-status", {
className={classnames('web3-status', {
'web3-status__connected': this.props.isConnected,
'web3-status--pending': hasPendingTransactions,
'web3-status--confirmed': hasConfirmedTransactions,
'web3-status--confirmed': hasConfirmedTransactions
})}
onClick={this.handleClick}
>
<div className="web3-status__text">
{hasPendingTransactions ? getPendingText(pending, t("pending")) : getText(address, t("disconnected")) }
{hasPendingTransactions ? getPendingText(pending, t('pending')) : getText(address, t('disconnected'))}
</div>
<div
className="web3-status__identicon"
ref={el => {
if (!el) {
return;
return
}
if (!address || address.length < 42 || !ethers.utils.isHexString(address)) {
return;
return
}
el.innerHTML = '';
el.appendChild(Jazzicon(16, parseInt(address.slice(2), 16)));
el.innerHTML = ''
el.appendChild(Jazzicon(16, parseInt(address.slice(2), 16)))
}}
/>
{this.renderModal()}
</div>
);
)
}
}
function getPendingText(pendingTransactions, pendingLabel) {
return (
<div className="web3-status__pending-container">
<div className="loader" />
<span key="text">{pendingTransactions.length} {pendingLabel}</span>
<span key="text">
{pendingTransactions.length} {pendingLabel}
</span>
</div>
);
)
}
function getText(text, disconnectedText) {
if (!text || text.length < 42 || !ethers.utils.isHexString(text)) {
return disconnectedText;
return disconnectedText
}
const address = ethers.utils.getAddress(text);
return `${address.substring(0, 6)}...${address.substring(38)}`;
const address = ethers.utils.getAddress(text)
return `${address.substring(0, 6)}...${address.substring(38)}`
}
Web3Status.propTypes = {
isConnected: PropTypes.bool,
address: PropTypes.string,
};
address: PropTypes.string
}
Web3Status.defaultProps = {
isConnected: false,
address: 'Disconnected',
};
address: 'Disconnected'
}
export default connect(
state => {
export default connect(state => {
return {
address: state.web3connect.account,
isConnected: !!(state.web3connect.web3 && state.web3connect.account),
pending: state.web3connect.transactions.pending,
confirmed: state.web3connect.transactions.confirmed,
};
confirmed: state.web3connect.transactions.confirmed
}
)(withNamespaces()(Web3Status));
})(withNamespaces()(Web3Status))

@ -1,12 +1,12 @@
@import "../../variables.scss";
@import '../../variables.scss';
.web3-status {
@extend %row-nowrap;
height: 2rem;
font-size: .9rem;
font-size: 0.9rem;
align-items: center;
border: 1px solid $mercury-gray;
padding: .5rem;
padding: 0.5rem;
border-radius: 2rem;
color: $dove-gray;
font-weight: 400;
@ -22,9 +22,9 @@
&__text {
flex: 1 1 auto;
overflow: hidden;
margin-right: .75rem;
margin-left: .25rem;
font-size: .75rem;
margin-right: 0.75rem;
margin-left: 0.25rem;
font-size: 0.75rem;
}
&__pending-container {
@ -39,7 +39,6 @@
}
}
.pending-modal {
background-color: $white;
position: relative;
@ -80,12 +79,12 @@
color: $royal-blue;
border: 1px solid $royal-blue;
background-color: $zumthor-blue;
padding: .5rem .75rem;;
padding: 0.5rem 0.75rem;
border-radius: 100px;
font-size: .75rem;
font-size: 0.75rem;
& > .loading {
margin-right: .5rem;
margin-right: 0.5rem;
}
}
@ -96,7 +95,6 @@
}
}
.pending-modal-appear {
bottom: 0;
}

@ -1,53 +1,53 @@
// string literals for actions
// set global web3 object
export const INITIALIZE_GLOBAL_WEB3 = 'INITIALIZE_GLOBAL_WEB3';
export const INITIALIZE_GLOBAL_WEB3 = 'INITIALIZE_GLOBAL_WEB3'
// web3 actions, all set from action creator to reducer to app
export const SET_WEB3_CONNECTION_STATUS = 'WEB3_CONNECTION_STATUS';
export const CHECK_WEB3_CONNECTION = 'CHECK_WEB3_CONNECTION';
export const SET_CURRENT_MASK_ADDRESS = 'SET_CURRENT_MASK_ADDRESS';
export const SET_WEB3_CONNECTION_STATUS = 'WEB3_CONNECTION_STATUS'
export const CHECK_WEB3_CONNECTION = 'CHECK_WEB3_CONNECTION'
export const SET_CURRENT_MASK_ADDRESS = 'SET_CURRENT_MASK_ADDRESS'
export const METAMASK_LOCKED = 'METAMASK_LOCKED';
export const METAMASK_UNLOCKED = 'METAMASK_UNLOCKED';
export const SET_INTERACTION_STATE = 'SET_INTERACTION_STATE';
export const SET_NETWORK_MESSAGE = 'SET_NETWORK_MESSAGE';
export const METAMASK_LOCKED = 'METAMASK_LOCKED'
export const METAMASK_UNLOCKED = 'METAMASK_UNLOCKED'
export const SET_INTERACTION_STATE = 'SET_INTERACTION_STATE'
export const SET_NETWORK_MESSAGE = 'SET_NETWORK_MESSAGE'
export const SET_BLOCK_TIMESTAMP = 'SET_BLOCK_TIMESTAMP';
export const SET_EXCHANGE_TYPE = 'SET_EXCHANGE_TYPE';
export const SET_BLOCK_TIMESTAMP = 'SET_BLOCK_TIMESTAMP'
export const SET_EXCHANGE_TYPE = 'SET_EXCHANGE_TYPE'
// actions to toggle divs
export const TOGGLE_ABOUT = 'TOGGLE_ABOUT';
export const TOGGLE_INVEST = 'TOGGLE_INVEST';
export const TOGGLE_ABOUT = 'TOGGLE_ABOUT'
export const TOGGLE_INVEST = 'TOGGLE_INVEST'
// CONTRACT actions in actions, action creator, reducer
export const FACTORY_CONTRACT_READY = 'FACTORY_CONTRACT_READY';
export const EXCHANGE_CONTRACT_READY = 'EXCHANGE_CONTRACT_READY';
export const TOKEN_CONTRACT_READY = 'TOKEN_CONTRACT_READY';
export const FACTORY_CONTRACT_READY = 'FACTORY_CONTRACT_READY'
export const EXCHANGE_CONTRACT_READY = 'EXCHANGE_CONTRACT_READY'
export const TOKEN_CONTRACT_READY = 'TOKEN_CONTRACT_READY'
// actions for the exchange
export const SET_INPUT_BALANCE = 'SET_INPUT_BALANCE';
export const SET_OUTPUT_BALANCE = 'SET_OUTPUT_BALANCE';
export const SET_INPUT_TOKEN = 'SET_INPUT_TOKEN';
export const SET_OUTPUT_TOKEN = 'SET_OUTPUT_TOKEN';
export const SET_ETH_POOL_1 = 'SET_ETH_POOL_1';
export const SET_ETH_POOL_2 = 'SET_ETH_POOL_2';
export const SET_TOKEN_POOL_1 = 'SET_TOKEN_POOL_1';
export const SET_TOKEN_POOL_2 = 'SET_TOKEN_POOL_2';
export const SET_ALLOWANCE_APPROVAL_STATE = 'SET_ALLOWANCE_APPROVAL_STATE';
export const SET_EXCHANGE_INPUT_VALUE = 'SET_EXCHANGE_INPUT_VALUE';
export const SET_EXCHANGE_OUTPUT_VALUE = 'SET_EXCHANGE_OUTPUT_VALUE';
export const SET_EXCHANGE_RATE = 'SET_EXCHANGE_RATE';
export const SET_EXCHANGE_FEE = 'SET_EXCHANGE_FEE';
export const SET_INVEST_TOKEN = 'SET_INVEST_TOKEN';
export const SET_INVEST_ETH_POOL = 'SET_INVEST_ETH';
export const SET_INVEST_TOKEN_POOL = 'SET_INVEST_TOKENS';
export const SET_INVEST_TOKEN_ALLOWANCE = 'SET_INVEST_TOKEN_ALLOWANCE';
export const SET_INVEST_SHARES = 'SET_INVEST_SHARES';
export const SET_USER_SHARES = 'SET_USER_SHARES';
export const SET_INVEST_TOKEN_BALANCE = 'SET_INVEST_TOKEN_BALANCE';
export const SET_INVEST_ETH_BALANCE = 'SET_INVEST_ETH_BALANCE';
export const SET_INVEST_SHARES_INPUT = 'SET_INVEST_SHARES_INPUT';
export const SET_INVEST_ETH_REQUIRED = 'SET_INVEST_ETH_REQUIRED';
export const SET_INVEST_TOKENS_REQUIRED = 'SET_INVEST_TOKENS_REQUIRED';
export const SET_INVEST_CHECKED = 'SET_INVEST_CHECKED';
export const SET_INPUT_BALANCE = 'SET_INPUT_BALANCE'
export const SET_OUTPUT_BALANCE = 'SET_OUTPUT_BALANCE'
export const SET_INPUT_TOKEN = 'SET_INPUT_TOKEN'
export const SET_OUTPUT_TOKEN = 'SET_OUTPUT_TOKEN'
export const SET_ETH_POOL_1 = 'SET_ETH_POOL_1'
export const SET_ETH_POOL_2 = 'SET_ETH_POOL_2'
export const SET_TOKEN_POOL_1 = 'SET_TOKEN_POOL_1'
export const SET_TOKEN_POOL_2 = 'SET_TOKEN_POOL_2'
export const SET_ALLOWANCE_APPROVAL_STATE = 'SET_ALLOWANCE_APPROVAL_STATE'
export const SET_EXCHANGE_INPUT_VALUE = 'SET_EXCHANGE_INPUT_VALUE'
export const SET_EXCHANGE_OUTPUT_VALUE = 'SET_EXCHANGE_OUTPUT_VALUE'
export const SET_EXCHANGE_RATE = 'SET_EXCHANGE_RATE'
export const SET_EXCHANGE_FEE = 'SET_EXCHANGE_FEE'
export const SET_INVEST_TOKEN = 'SET_INVEST_TOKEN'
export const SET_INVEST_ETH_POOL = 'SET_INVEST_ETH'
export const SET_INVEST_TOKEN_POOL = 'SET_INVEST_TOKENS'
export const SET_INVEST_TOKEN_ALLOWANCE = 'SET_INVEST_TOKEN_ALLOWANCE'
export const SET_INVEST_SHARES = 'SET_INVEST_SHARES'
export const SET_USER_SHARES = 'SET_USER_SHARES'
export const SET_INVEST_TOKEN_BALANCE = 'SET_INVEST_TOKEN_BALANCE'
export const SET_INVEST_ETH_BALANCE = 'SET_INVEST_ETH_BALANCE'
export const SET_INVEST_SHARES_INPUT = 'SET_INVEST_SHARES_INPUT'
export const SET_INVEST_ETH_REQUIRED = 'SET_INVEST_ETH_REQUIRED'
export const SET_INVEST_TOKENS_REQUIRED = 'SET_INVEST_TOKENS_REQUIRED'
export const SET_INVEST_CHECKED = 'SET_INVEST_CHECKED'

@ -1 +1 @@
export const INSUFFICIENT_BALANCE = 'Insufficient balance';
export const INSUFFICIENT_BALANCE = 'Insufficient balance'

@ -2,30 +2,30 @@ const RINKEBY = {
factoryAddress: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
exchangeAddresses: {
addresses: [
['BAT','0x9B913956036a3462330B0642B20D3879ce68b450'],
['DAI','0x77dB9C915809e7BE439D2AB21032B1b8B58F6891'],
['MKR','0x93bB63aFe1E0180d0eF100D774B473034fd60C36'],
['OMG','0x26C226EBb6104676E593F8A070aD6f25cDa60F8D'],
['BAT', '0x9B913956036a3462330B0642B20D3879ce68b450'],
['DAI', '0x77dB9C915809e7BE439D2AB21032B1b8B58F6891'],
['MKR', '0x93bB63aFe1E0180d0eF100D774B473034fd60C36'],
['OMG', '0x26C226EBb6104676E593F8A070aD6f25cDa60F8D']
// ['ZRX','0xaBD44a1D1b9Fb0F39fE1D1ee6b1e2a14916D067D'],
],
fromToken: {
'0xDA5B056Cfb861282B4b59d29c9B395bcC238D29B': '0x9B913956036a3462330B0642B20D3879ce68b450',
'0x2448eE2641d78CC42D7AD76498917359D961A783': '0x77dB9C915809e7BE439D2AB21032B1b8B58F6891',
'0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85': '0x93bB63aFe1E0180d0eF100D774B473034fd60C36',
'0x879884c3C46A24f56089f3bBbe4d5e38dB5788C0': '0x26C226EBb6104676E593F8A070aD6f25cDa60F8D',
'0x879884c3C46A24f56089f3bBbe4d5e38dB5788C0': '0x26C226EBb6104676E593F8A070aD6f25cDa60F8D'
// '0xF22e3F33768354c9805d046af3C0926f27741B43': '0xaBD44a1D1b9Fb0F39fE1D1ee6b1e2a14916D067D',
},
}
},
tokenAddresses: {
addresses: [
['BAT','0xDA5B056Cfb861282B4b59d29c9B395bcC238D29B'],
['DAI','0x2448eE2641d78CC42D7AD76498917359D961A783'],
['MKR','0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'],
['OMG','0x879884c3C46A24f56089f3bBbe4d5e38dB5788C0'],
['BAT', '0xDA5B056Cfb861282B4b59d29c9B395bcC238D29B'],
['DAI', '0x2448eE2641d78CC42D7AD76498917359D961A783'],
['MKR', '0xF9bA5210F91D0474bd1e1DcDAeC4C58E359AaD85'],
['OMG', '0x879884c3C46A24f56089f3bBbe4d5e38dB5788C0']
// ['ZRX','0xF22e3F33768354c9805d046af3C0926f27741B43'],
],
},
};
]
}
}
const MAIN = {
factoryAddress: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95',
@ -73,7 +73,7 @@ const MAIN = {
['WETH', '0xA2881A90Bf33F03E7a3f803765Cd2ED5c8928dFb'],
['XCHF', '0x8dE0d002DC83478f479dC31F76cB0a8aa7CcEa17'],
['ZIL', '0x7dc095A5CF7D6208CC680fA9866F80a53911041a'],
['ZRX', '0xaE76c84C9262Cdb9abc0C2c8888e62Db8E22A0bF'],
['ZRX', '0xaE76c84C9262Cdb9abc0C2c8888e62Db8E22A0bF']
],
fromToken: {
'0x960b236A07cf122663c4303350609A66A7B288C0': '0x077d52B047735976dfdA76feF74d4d988AC25196',
@ -118,8 +118,8 @@ const MAIN = {
'0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27': '0x7dc095A5CF7D6208CC680fA9866F80a53911041a',
'0xE41d2489571d322189246DaFA5ebDe1F4699F498': '0xaE76c84C9262Cdb9abc0C2c8888e62Db8E22A0bF',
'0x3772f9716Cf6D7a09edE3587738AA2af5577483a': '0x5d8888a212d033cff5f2e0ac24ad91a5495bad62',
'0x0cbe2df57ca9191b64a7af3baa3f946fa7df2f25': '0xa1ecdcca26150cf69090280ee2ee32347c238c7b',
},
'0x0cbe2df57ca9191b64a7af3baa3f946fa7df2f25': '0xa1ecdcca26150cf69090280ee2ee32347c238c7b'
}
},
tokenAddresses: {
addresses: [
@ -165,25 +165,27 @@ const MAIN = {
['WETH', '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'],
['XCHF', '0xB4272071eCAdd69d933AdcD19cA99fe80664fc08'],
['ZIL', '0x05f4a42e251f2d52b8ed15E9FEdAacFcEF1FAD27'],
['ZRX', '0xE41d2489571d322189246DaFA5ebDe1F4699F498'],
],
},
};
['ZRX', '0xE41d2489571d322189246DaFA5ebDe1F4699F498']
]
}
}
const SET_ADDRESSES = 'app/addresses/setAddresses';
const ADD_EXCHANGE = 'app/addresses/addExchange';
const SET_ADDRESSES = 'app/addresses/setAddresses'
const ADD_EXCHANGE = 'app/addresses/addExchange'
const initialState = RINKEBY;
const initialState = RINKEBY
export const addExchange = ({label, exchangeAddress, tokenAddress}) => (dispatch, getState) => {
const { addresses: { tokenAddresses, exchangeAddresses } } = getState();
export const addExchange = ({ label, exchangeAddress, tokenAddress }) => (dispatch, getState) => {
const {
addresses: { tokenAddresses, exchangeAddresses }
} = getState()
if (tokenAddresses.addresses.filter(([ symbol ]) => symbol === label).length) {
return;
if (tokenAddresses.addresses.filter(([symbol]) => symbol === label).length) {
return
}
if (exchangeAddresses.fromToken[tokenAddresses]) {
return;
return
}
dispatch({
@ -191,68 +193,62 @@ export const addExchange = ({label, exchangeAddress, tokenAddress}) => (dispatch
payload: {
label,
exchangeAddress,
tokenAddress,
},
});
};
tokenAddress
}
})
}
export const setAddresses = networkId => {
switch(networkId) {
switch (networkId) {
// Main Net
case 1:
case '1':
return {
type: SET_ADDRESSES,
payload: MAIN,
};
payload: MAIN
}
// Rinkeby
case 4:
case '4':
default:
return {
type: SET_ADDRESSES,
payload: RINKEBY,
};
payload: RINKEBY
}
};
}
}
export default (state = initialState, { type, payload }) => {
switch (type) {
case SET_ADDRESSES:
return payload;
return payload
case ADD_EXCHANGE:
return handleAddExchange(state, { payload });
return handleAddExchange(state, { payload })
default:
return state;
return state
}
}
function handleAddExchange(state, { payload }) {
const { label, tokenAddress, exchangeAddress } = payload;
const { label, tokenAddress, exchangeAddress } = payload
if (!label || !tokenAddress || !exchangeAddress) {
return state;
return state
}
return {
...state,
exchangeAddresses: {
...state.exchangeAddresses,
addresses: [
...state.exchangeAddresses.addresses,
[label, exchangeAddress]
],
addresses: [...state.exchangeAddresses.addresses, [label, exchangeAddress]],
fromToken: {
...state.exchangeAddresses.fromToken,
[tokenAddress]: exchangeAddress,
},
[tokenAddress]: exchangeAddress
}
},
tokenAddresses: {
...state.tokenAddresses,
addresses: [
...state.tokenAddresses.addresses,
[label, tokenAddress]
],
},
};
addresses: [...state.tokenAddresses.addresses, [label, tokenAddress]]
}
}
}

@ -1,16 +1,16 @@
const DISMISS_BETA_MESSAGE = 'app/app/dismissBetaMessage';
const DISMISS_BETA_MESSAGE = 'app/app/dismissBetaMessage'
const initialState = {
showBetaMessage: true,
};
showBetaMessage: true
}
export const dismissBetaMessage = () => ({ type: DISMISS_BETA_MESSAGE });
export const dismissBetaMessage = () => ({ type: DISMISS_BETA_MESSAGE })
export default function appReducer(state = initialState, { type, payload }) {
switch (type) {
case DISMISS_BETA_MESSAGE:
return { ...state, showBetaMessage: false };
return { ...state, showBetaMessage: false }
default:
return state;
return state
}
}

@ -1,12 +1,12 @@
import { combineReducers } from 'redux';
import addresses from './addresses';
import app from './app';
import pending from './pending';
import web3connect from './web3connect';
import { combineReducers } from 'redux'
import addresses from './addresses'
import app from './app'
import pending from './pending'
import web3connect from './web3connect'
export default combineReducers({
app,
addresses,
pending,
web3connect,
});
web3connect
})

@ -1,15 +1,15 @@
const ADD_APPROVAL_TX = 'app/send/addApprovalTx';
const ADD_APPROVAL_TX = 'app/send/addApprovalTx'
const getInitialState = () => {
return {
approvals: {},
};
};
approvals: {}
}
}
export const addApprovalTx = ({ tokenAddress, txId }) => ({
type: ADD_APPROVAL_TX,
payload: { tokenAddress, txId },
});
payload: { tokenAddress, txId }
})
export default function sendReducer(state = getInitialState(), { type, payload }) {
switch (type) {
@ -17,10 +17,10 @@ export default function sendReducer(state = getInitialState(), { type, payload }
return {
approvals: {
...state.approvals,
[payload.tokenAddress]: payload.txId,
[payload.tokenAddress]: payload.txId
}
}
};
default:
return state;
return state
}
}

@ -1,24 +1,24 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {BigNumber as BN} from 'bignumber.js';
import Web3 from 'web3';
import ERC20_ABI from "../abi/erc20";
import ERC20_WITH_BYTES_ABI from "../abi/erc20_symbol_bytes32";
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { BigNumber as BN } from 'bignumber.js'
import Web3 from 'web3'
import ERC20_ABI from '../abi/erc20'
import ERC20_WITH_BYTES_ABI from '../abi/erc20_symbol_bytes32'
export const INITIALIZE = 'we3connect/initialize';
export const UPDATE_ACCOUNT = 'we3connect/updateAccount';
export const WATCH_ETH_BALANCE = 'web3connect/watchEthBalance';
export const WATCH_TOKEN_BALANCE = 'web3connect/watchTokenBalance';
export const UPDATE_ETH_BALANCE = 'web3connect/updateEthBalance';
export const UPDATE_TOKEN_BALANCE = 'web3connect/updateTokenBalance';
export const WATCH_APPROVALS = 'web3connect/watchApprovals';
export const UPDATE_APPROVALS = 'web3connect/updateApprovals';
export const ADD_CONTRACT = 'web3connect/addContract';
export const UPDATE_NETWORK_ID = 'web3connect/updateNetworkId';
export const ADD_PENDING_TX = 'web3connect/addPendingTx';
export const REMOVE_PENDING_TX = 'web3connect/removePendingTx';
export const ADD_CONFIRMED_TX = 'web3connect/addConfirmedTx';
export const INITIALIZE = 'we3connect/initialize'
export const UPDATE_ACCOUNT = 'we3connect/updateAccount'
export const WATCH_ETH_BALANCE = 'web3connect/watchEthBalance'
export const WATCH_TOKEN_BALANCE = 'web3connect/watchTokenBalance'
export const UPDATE_ETH_BALANCE = 'web3connect/updateEthBalance'
export const UPDATE_TOKEN_BALANCE = 'web3connect/updateTokenBalance'
export const WATCH_APPROVALS = 'web3connect/watchApprovals'
export const UPDATE_APPROVALS = 'web3connect/updateApprovals'
export const ADD_CONTRACT = 'web3connect/addContract'
export const UPDATE_NETWORK_ID = 'web3connect/updateNetworkId'
export const ADD_PENDING_TX = 'web3connect/addPendingTx'
export const REMOVE_PENDING_TX = 'web3connect/removePendingTx'
export const ADD_CONFIRMED_TX = 'web3connect/addConfirmedTx'
const initialState = {
web3: null,
@ -26,181 +26,183 @@ const initialState = {
initialized: false,
account: '',
balances: {
ethereum: {},
ethereum: {}
},
approvals: {
'0x0': {
TOKEN_OWNER: {
SPENDER: {},
},
},
SPENDER: {}
}
}
},
transactions: {
pending: [],
confirmed: [],
confirmed: []
},
watched: {
balances: {
ethereum: [],
ethereum: []
},
approvals: {},
approvals: {}
},
contracts: {},
};
contracts: {}
}
// selectors
export const selectors = () => (dispatch, getState) => {
const state = getState().web3connect;
const state = getState().web3connect
const getTokenBalance = (tokenAddress, address) => {
const tokenBalances = state.balances[tokenAddress] || {};
const balance = tokenBalances[address];
const tokenBalances = state.balances[tokenAddress] || {}
const balance = tokenBalances[address]
if (!balance) {
dispatch(watchBalance({ balanceOf: address, tokenAddress }));
return Balance(0);
dispatch(watchBalance({ balanceOf: address, tokenAddress }))
return Balance(0)
}
return balance
}
return balance;
};
const getBalance = (address, tokenAddress) => {
if (process.env.NODE_ENV !== 'production' && !tokenAddress) {
console.warn('No token address found - return ETH balance');
console.warn('No token address found - return ETH balance')
}
if (!tokenAddress || tokenAddress === 'ETH') {
const balance = state.balances.ethereum[address];
const balance = state.balances.ethereum[address]
if (!balance) {
dispatch(watchBalance({ balanceOf: address }));
return Balance(0, 'ETH');
dispatch(watchBalance({ balanceOf: address }))
return Balance(0, 'ETH')
}
return balance;
return balance
} else if (tokenAddress) {
return getTokenBalance(tokenAddress, address);
return getTokenBalance(tokenAddress, address)
}
return Balance(NaN);
};
return Balance(NaN)
}
const getApprovals = (tokenAddress, tokenOwner, spender) => {
const token = state.approvals[tokenAddress] || {};
const owner = token[tokenOwner] || {};
const token = state.approvals[tokenAddress] || {}
const owner = token[tokenOwner] || {}
if (!owner[spender]) {
dispatch(watchApprovals({ tokenAddress, tokenOwner, spender }));
return Balance(0);
dispatch(watchApprovals({ tokenAddress, tokenOwner, spender }))
return Balance(0)
}
return owner[spender];
};
return owner[spender]
}
return {
getBalance,
getTokenBalance,
getApprovals,
};
};
getApprovals
}
}
const Balance = (value, label = '', decimals = 0) => ({
value: BN(value),
label: label.toUpperCase(),
decimals: +decimals,
});
decimals: +decimals
})
export const initialize = () => (dispatch, getState) => {
const { web3connect } = getState();
const { web3connect } = getState()
return new Promise(async (resolve, reject) => {
if (web3connect.web3) {
resolve(web3connect.web3);
return;
resolve(web3connect.web3)
return
}
if (typeof window.ethereum !== 'undefined') {
try {
const web3 = new Web3(window.ethereum);
await window.ethereum.enable();
const web3 = new Web3(window.ethereum)
await window.ethereum.enable()
dispatch({
type: INITIALIZE,
payload: web3,
});
resolve(web3);
return;
payload: web3
})
resolve(web3)
return
} catch (error) {
console.error('User denied access.');
dispatch({ type: INITIALIZE });
reject();
return;
console.error('User denied access.')
dispatch({ type: INITIALIZE })
reject()
return
}
}
if (typeof window.web3 !== 'undefined') {
const web3 = new Web3(window.web3.currentProvider);
const web3 = new Web3(window.web3.currentProvider)
dispatch({
type: INITIALIZE,
payload: web3,
});
resolve(web3);
return;
payload: web3
})
resolve(web3)
return
}
dispatch({ type: INITIALIZE });
reject();
dispatch({ type: INITIALIZE })
reject()
})
};
}
export const watchBalance = ({ balanceOf, tokenAddress }) => (dispatch, getState) => {
if (!balanceOf) {
return;
return
}
const { web3connect } = getState();
const { watched } = web3connect;
const { web3connect } = getState()
const { watched } = web3connect
if (!tokenAddress) {
if (watched.balances.ethereum.includes(balanceOf)) {
return;
return
}
dispatch({
type: WATCH_ETH_BALANCE,
payload: balanceOf,
});
setTimeout(() => dispatch(sync()), 0);
payload: balanceOf
})
setTimeout(() => dispatch(sync()), 0)
} else if (tokenAddress) {
if (watched.balances[tokenAddress] && watched.balances[tokenAddress].includes(balanceOf)) {
return;
return
}
dispatch({
type: WATCH_TOKEN_BALANCE,
payload: {
tokenAddress,
balanceOf,
},
});
setTimeout(() => dispatch(sync()), 0);
balanceOf
}
};
})
setTimeout(() => dispatch(sync()), 0)
}
}
export const watchApprovals = ({ tokenAddress, tokenOwner, spender }) => (dispatch, getState) => {
const { web3connect: { watched } } = getState();
const token = watched.approvals[tokenAddress] || {};
const owner = token[tokenOwner] || [];
const {
web3connect: { watched }
} = getState()
const token = watched.approvals[tokenAddress] || {}
const owner = token[tokenOwner] || []
if (owner.includes(spender)) {
return;
return
}
return dispatch({
type: WATCH_APPROVALS,
payload: {
tokenAddress,
tokenOwner,
spender,
},
});
};
spender
}
})
}
export const addPendingTx = txId => ({
type: ADD_PENDING_TX,
payload: txId,
});
payload: txId
})
export const updateApprovals = ({ tokenAddress, tokenOwner, spender, balance }) => ({
type: UPDATE_APPROVALS,
@ -208,90 +210,100 @@ export const updateApprovals = ({ tokenAddress, tokenOwner, spender, balance })
tokenAddress,
tokenOwner,
spender,
balance,
},
});
balance
}
})
export const sync = () => async (dispatch, getState) => {
const { getBalance, getApprovals } = dispatch(selectors());
const web3 = await dispatch(initialize());
const { getBalance, getApprovals } = dispatch(selectors())
const web3 = await dispatch(initialize())
const {
account,
watched,
contracts,
networkId,
transactions: { pending },
} = getState().web3connect;
transactions: { pending }
} = getState().web3connect
// Sync Account
const accounts = await web3.eth.getAccounts();
const accounts = await web3.eth.getAccounts()
if (account !== accounts[0]) {
dispatch({ type: UPDATE_ACCOUNT, payload: accounts[0] });
dispatch(watchBalance({ balanceOf: accounts[0] }));
dispatch({ type: UPDATE_ACCOUNT, payload: accounts[0] })
dispatch(watchBalance({ balanceOf: accounts[0] }))
}
if (!networkId) {
dispatch({
type: UPDATE_NETWORK_ID,
payload: await web3.eth.net.getId(),
});
payload: await web3.eth.net.getId()
})
}
// Sync Ethereum Balances
watched.balances.ethereum.forEach(async address => {
const balance = await web3.eth.getBalance(address);
const { value } = getBalance(address);
const balance = await web3.eth.getBalance(address)
const { value } = getBalance(address)
if (value.isEqualTo(BN(balance))) {
return;
return
}
dispatch({
type: UPDATE_ETH_BALANCE,
payload: {
balance: Balance(balance, 'ETH', 18),
balanceOf: address,
},
balanceOf: address
}
})
})
});
// Sync Token Balances
Object.keys(watched.balances)
.forEach(tokenAddress => {
Object.keys(watched.balances).forEach(tokenAddress => {
if (tokenAddress === 'ethereum') {
return;
return
}
const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress);
const contractBytes32 = contracts[tokenAddress] || new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress);
const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress)
const contractBytes32 = contracts[tokenAddress] || new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress)
if (!contracts[tokenAddress]) {
dispatch({
type: ADD_CONTRACT,
payload: {
address: tokenAddress,
contract: contract,
},
});
contract: contract
}
})
}
const watchlist = watched.balances[tokenAddress] || [];
const watchlist = watched.balances[tokenAddress] || []
watchlist.forEach(async address => {
const tokenBalance = getBalance(address, tokenAddress);
const balance = await contract.methods.balanceOf(address).call();
const decimals = tokenBalance.decimals || await contract.methods.decimals().call();
let symbol = tokenBalance.symbol;
const tokenBalance = getBalance(address, tokenAddress)
const balance = await contract.methods.balanceOf(address).call()
const decimals = tokenBalance.decimals || (await contract.methods.decimals().call())
let symbol = tokenBalance.symbol
try {
symbol = symbol || await contract.methods.symbol().call().catch();
symbol =
symbol ||
(await contract.methods
.symbol()
.call()
.catch())
} catch (e) {
try {
symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call().catch());
} catch (err) {
}
symbol =
symbol ||
web3.utils.hexToString(
await contractBytes32.methods
.symbol()
.call()
.catch()
)
} catch (err) {}
}
if (tokenBalance.value.isEqualTo(BN(balance)) && tokenBalance.label && tokenBalance.decimals) {
return;
return
}
dispatch({
@ -299,90 +311,86 @@ export const sync = () => async (dispatch, getState) => {
payload: {
tokenAddress,
balanceOf: address,
balance: Balance(balance, symbol, decimals),
},
});
});
});
balance: Balance(balance, symbol, decimals)
}
})
})
})
// Update Approvals
Object.entries(watched.approvals)
.forEach(([tokenAddress, token]) => {
const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress);
const contractBytes32 = contracts[tokenAddress] || new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress);
Object.entries(watched.approvals).forEach(([tokenAddress, token]) => {
const contract = contracts[tokenAddress] || new web3.eth.Contract(ERC20_ABI, tokenAddress)
const contractBytes32 = contracts[tokenAddress] || new web3.eth.Contract(ERC20_WITH_BYTES_ABI, tokenAddress)
Object.entries(token)
.forEach(([ tokenOwnerAddress, tokenOwner ]) => {
Object.entries(token).forEach(([tokenOwnerAddress, tokenOwner]) => {
tokenOwner.forEach(async spenderAddress => {
const approvalBalance = getApprovals(tokenAddress, tokenOwnerAddress, spenderAddress);
const balance = await contract.methods.allowance(tokenOwnerAddress, spenderAddress).call();
const decimals = approvalBalance.decimals || await contract.methods.decimals().call();
let symbol = approvalBalance.label;
const approvalBalance = getApprovals(tokenAddress, tokenOwnerAddress, spenderAddress)
const balance = await contract.methods.allowance(tokenOwnerAddress, spenderAddress).call()
const decimals = approvalBalance.decimals || (await contract.methods.decimals().call())
let symbol = approvalBalance.label
try {
symbol = symbol || await contract.methods.symbol().call();
symbol = symbol || (await contract.methods.symbol().call())
} catch (e) {
try {
symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call());
} catch (err) {
}
symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call())
} catch (err) {}
}
if (approvalBalance.label && approvalBalance.value.isEqualTo(BN(balance))) {
return;
return
}
dispatch(updateApprovals({
dispatch(
updateApprovals({
tokenAddress,
tokenOwner: tokenOwnerAddress,
spender: spenderAddress,
balance: Balance(balance, symbol, decimals),
}));
});
});
});
balance: Balance(balance, symbol, decimals)
})
)
})
})
})
pending.forEach(async txId => {
try {
const data = await web3.eth.getTransactionReceipt(txId) || {};
const data = (await web3.eth.getTransactionReceipt(txId)) || {}
// If data is an empty obj, then it's still pending.
if (!('status' in data)) {
return;
return
}
dispatch({
type: REMOVE_PENDING_TX,
payload: txId,
});
payload: txId
})
if (data.status) {
dispatch({
type: ADD_CONFIRMED_TX,
payload: txId,
});
payload: txId
})
} else {
// TODO: dispatch ADD_REJECTED_TX
}
} catch (err) {
dispatch({
type: REMOVE_PENDING_TX,
payload: txId,
});
payload: txId
})
// TODO: dispatch ADD_REJECTED_TX
}
});
};
})
}
export const startWatching = () => async (dispatch, getState) => {
const { account } = getState().web3connect;
const timeout = !account
? 1000
: 5000;
const { account } = getState().web3connect
const timeout = !account ? 1000 : 5000
dispatch(sync());
setTimeout(() => dispatch(startWatching()), timeout);
};
dispatch(sync())
setTimeout(() => dispatch(startWatching()), timeout)
}
export default function web3connectReducer(state = initialState, { type, payload }) {
switch (type) {
@ -390,13 +398,13 @@ export default function web3connectReducer(state = initialState, { type, payload
return {
...state,
web3: payload,
initialized: true,
};
initialized: true
}
case UPDATE_ACCOUNT:
return {
...state,
account: payload,
};
account: payload
}
case WATCH_ETH_BALANCE:
return {
...state,
@ -404,14 +412,14 @@ export default function web3connectReducer(state = initialState, { type, payload
...state.watched,
balances: {
...state.watched.balances,
ethereum: [ ...state.watched.balances.ethereum, payload ],
},
},
};
ethereum: [...state.watched.balances.ethereum, payload]
}
}
}
case WATCH_TOKEN_BALANCE:
const { watched } = state;
const { balances } = watched;
const watchlist = balances[payload.tokenAddress] || [];
const { watched } = state
const { balances } = watched
const watchlist = balances[payload.tokenAddress] || []
return {
...state,
@ -419,10 +427,10 @@ export default function web3connectReducer(state = initialState, { type, payload
...watched,
balances: {
...balances,
[payload.tokenAddress]: [ ...watchlist, payload.balanceOf ],
},
},
};
[payload.tokenAddress]: [...watchlist, payload.balanceOf]
}
}
}
case UPDATE_ETH_BALANCE:
return {
...state,
@ -430,33 +438,33 @@ export default function web3connectReducer(state = initialState, { type, payload
...state.balances,
ethereum: {
...state.balances.ethereum,
[payload.balanceOf]: payload.balance,
},
},
};
[payload.balanceOf]: payload.balance
}
}
}
case UPDATE_TOKEN_BALANCE:
const tokenBalances = state.balances[payload.tokenAddress] || {};
const tokenBalances = state.balances[payload.tokenAddress] || {}
return {
...state,
balances: {
...state.balances,
[payload.tokenAddress]: {
...tokenBalances,
[payload.balanceOf]: payload.balance,
},
},
};
[payload.balanceOf]: payload.balance
}
}
}
case ADD_CONTRACT:
return {
...state,
contracts: {
...state.contracts,
[payload.address]: payload.contract,
},
};
[payload.address]: payload.contract
}
}
case WATCH_APPROVALS:
const token = state.watched.approvals[payload.tokenAddress] || {};
const tokenOwner = token[payload.tokenOwner] || [];
const token = state.watched.approvals[payload.tokenAddress] || {}
const tokenOwner = token[payload.tokenOwner] || []
return {
...state,
@ -466,14 +474,14 @@ export default function web3connectReducer(state = initialState, { type, payload
...state.watched.approvals,
[payload.tokenAddress]: {
...token,
[payload.tokenOwner]: [ ...tokenOwner, payload.spender ],
},
},
},
};
[payload.tokenOwner]: [...tokenOwner, payload.spender]
}
}
}
}
case UPDATE_APPROVALS:
const erc20 = state.approvals[payload.tokenAddress] || {};
const erc20Owner = erc20[payload.tokenOwner] || {};
const erc20 = state.approvals[payload.tokenAddress] || {}
const erc20Owner = erc20[payload.tokenOwner] || {}
return {
...state,
@ -483,72 +491,71 @@ export default function web3connectReducer(state = initialState, { type, payload
...erc20,
[payload.tokenOwner]: {
...erc20Owner,
[payload.spender]: payload.balance,
},
},
},
};
[payload.spender]: payload.balance
}
}
}
}
case UPDATE_NETWORK_ID:
return { ...state, networkId: payload };
return { ...state, networkId: payload }
case ADD_PENDING_TX:
return {
...state,
transactions: {
...state.transactions,
pending: [ ...state.transactions.pending, payload ],
},
};
pending: [...state.transactions.pending, payload]
}
}
case REMOVE_PENDING_TX:
return {
...state,
transactions: {
...state.transactions,
pending: state.transactions.pending.filter(id => id !== payload),
},
};
pending: state.transactions.pending.filter(id => id !== payload)
}
}
case ADD_CONFIRMED_TX:
if (state.transactions.confirmed.includes(payload)) {
return state;
return state
}
return {
...state,
transactions: {
...state.transactions,
confirmed: [ ...state.transactions.confirmed, payload ],
},
};
confirmed: [...state.transactions.confirmed, payload]
}
}
default:
return state;
return state
}
}
// Connect Component
export class _Web3Connect extends Component {
static propTypes = {
initialize: PropTypes.func.isRequired,
};
initialize: PropTypes.func.isRequired
}
static defaultProps = {
initialize() {}
};
}
componentWillMount() {
this.props.initialize()
.then(this.props.startWatching());
this.props.initialize().then(this.props.startWatching())
}
render() {
return <noscript />;
return <noscript />
}
}
export const Web3Connect = connect(
({ web3connect }) => ({
web3: web3connect.web3,
web3: web3connect.web3
}),
dispatch => ({
initialize: () => dispatch(initialize()),
startWatching: () => dispatch(startWatching()),
}),
)(_Web3Connect);
startWatching: () => dispatch(startWatching())
})
)(_Web3Connect)

@ -1,4 +1,4 @@
export default function (matchmask = [], minMatchCharLength = 1) {
export default function(matchmask = [], minMatchCharLength = 1) {
let matchedIndices = []
let start = -1
let end = -1
@ -10,7 +10,7 @@ export default function (matchmask = [], minMatchCharLength = 1) {
start = i
} else if (!match && start !== -1) {
end = i - 1
if ((end - start) + 1 >= minMatchCharLength) {
if (end - start + 1 >= minMatchCharLength) {
matchedIndices.push([start, end])
}
start = -1
@ -18,7 +18,7 @@ export default function (matchmask = [], minMatchCharLength = 1) {
}
// (i-1 - start) + 1 => i - start
if (matchmask[i - 1] && (i - start) >= minMatchCharLength) {
if (matchmask[i - 1] && i - start >= minMatchCharLength) {
matchedIndices.push([start, i - 1])
}

@ -1,4 +1,4 @@
export default function (pattern) {
export default function(pattern) {
let mask = {}
let len = pattern.length

@ -1,7 +1,7 @@
// eslint-disable-next-line no-useless-escape
const SPECIAL_CHARS_REGEX = /[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g
export default function (text, pattern, tokenSeparator = / +/g) {
export default function(text, pattern, tokenSeparator = / +/g) {
let regex = new RegExp(pattern.replace(SPECIAL_CHARS_REGEX, '\\$&').replace(tokenSeparator, '|'))
let matches = text.match(regex)
let isMatch = !!matches

@ -1,4 +1,4 @@
export default function (pattern, { errors = 0, currentLocation = 0, expectedLocation = 0, distance = 100 }) {
export default function(pattern, { errors = 0, currentLocation = 0, expectedLocation = 0, distance = 100 }) {
const accuracy = errors / pattern.length
const proximity = Math.abs(expectedLocation - currentLocation)
@ -7,5 +7,5 @@ export default function (pattern, { errors = 0, currentLocation = 0, expectedLoc
return proximity ? 1.0 : accuracy
}
return accuracy + (proximity / distance)
return accuracy + proximity / distance
}

@ -1,7 +1,12 @@
import bitapScore from './bitap_score';
import matchedIndices from './bitap_matched_indices';
import bitapScore from './bitap_score'
import matchedIndices from './bitap_matched_indices'
export default function (text, pattern, patternAlphabet, { location = 0, distance = 100, threshold = 0.6, findAllMatches = false, minMatchCharLength = 1 }) {
export default function(
text,
pattern,
patternAlphabet,
{ location = 0, distance = 100, threshold = 0.6, findAllMatches = false, minMatchCharLength = 1 }
) {
const expectedLocation = location
// Set starting location at beginning text and initialize the alphabet.
const textLen = text.length
@ -63,7 +68,7 @@ export default function (text, pattern, patternAlphabet, { location = 0, distanc
currentLocation: expectedLocation + binMid,
expectedLocation,
distance
});
})
if (score <= currentThreshold) {
binMin = binMid
@ -98,7 +103,7 @@ export default function (text, pattern, patternAlphabet, { location = 0, distanc
// Subsequent passes: fuzzy match
if (i !== 0) {
bitArr[j] |= (((lastBitArr[j + 1] | lastBitArr[j]) << 1) | 1) | lastBitArr[j + 1]
bitArr[j] |= ((lastBitArr[j + 1] | lastBitArr[j]) << 1) | 1 | lastBitArr[j + 1]
}
if (bitArr[j] & mask) {

@ -1,9 +1,11 @@
import bitapRegexSearch from './bitap_regex_search';
import bitapSearch from './bitap_search';
import patternAlphabet from './bitap_pattern_alphabet';
import bitapRegexSearch from './bitap_regex_search'
import bitapSearch from './bitap_search'
import patternAlphabet from './bitap_pattern_alphabet'
class Bitap {
constructor (pattern, {
constructor(
pattern,
{
// Approximately where in the text is the pattern expected to be found?
location = 0,
// Determines how close the match must be to the fuzzy location (specified above).
@ -26,7 +28,8 @@ class Bitap {
findAllMatches = false,
// Minimum number of characters that must be matched before a result is considered a match
minMatchCharLength = 1
}) {
}
) {
this.options = {
location,
distance,
@ -45,7 +48,7 @@ class Bitap {
}
}
search (text) {
search(text) {
if (!this.options.isCaseSensitive) {
text = text.toLowerCase()
}

@ -1 +1 @@
module.exports = obj => !Array.isArray ? Object.prototype.toString.call(obj) === '[object Array]' : Array.isArray(obj)
module.exports = obj => (!Array.isArray ? Object.prototype.toString.call(obj) === '[object Array]' : Array.isArray(obj))

@ -1,9 +1,11 @@
import Bitap from'./bitap';
import Bitap from './bitap'
const deepValue = require('./helpers/deep_value')
const isArray = require('./helpers/is_array')
class Fuse {
constructor (list, {
constructor(
list,
{
// Approximately where in the text is the pattern expected to be found?
location = 0,
// Determines how close the match must be to the fuzzy location (specified above).
@ -37,7 +39,7 @@ class Fuse {
// The default will search nested paths *ie foo.bar.baz*
getFn = deepValue,
// Default sort function
sortFn = (a, b) => (a.score - b.score),
sortFn = (a, b) => a.score - b.score,
// When true, the search algorithm will search individual words **and** the full string,
// computing the final score as a function of both. Note that when `tokenize` is `true`,
// the `threshold`, `distance`, and `location` are inconsequential for individual tokens.
@ -51,7 +53,8 @@ class Fuse {
// Will print to the console. Useful for debugging.
verbose = false
}) {
}
) {
this.options = {
location,
distance,
@ -76,18 +79,15 @@ class Fuse {
this.setCollection(list)
}
setCollection (list) {
setCollection(list) {
this.list = list
return list
}
search (pattern) {
search(pattern) {
this._log(`---------\nSearch pattern: "${pattern}"`)
const {
tokenSearchers,
fullSearcher
} = this._prepareSearchers(pattern)
const { tokenSearchers, fullSearcher } = this._prepareSearchers(pattern)
let { weights, results } = this._search(tokenSearchers, fullSearcher)
@ -100,7 +100,7 @@ class Fuse {
return this._format(results)
}
_prepareSearchers (pattern = '') {
_prepareSearchers(pattern = '') {
const tokenSearchers = []
if (this.options.tokenize) {
@ -116,7 +116,7 @@ class Fuse {
return { tokenSearchers, fullSearcher }
}
_search (tokenSearchers = [], fullSearcher) {
_search(tokenSearchers = [], fullSearcher) {
const list = this.list
const resultMap = {}
const results = []
@ -126,17 +126,20 @@ class Fuse {
if (typeof list[0] === 'string') {
// Iterate over every item
for (let i = 0, len = list.length; i < len; i += 1) {
this._analyze({
this._analyze(
{
key: '',
value: list[i],
record: i,
index: i
}, {
},
{
resultMap,
results,
tokenSearchers,
fullSearcher
})
}
)
}
return { weights: null, results }
@ -152,7 +155,7 @@ class Fuse {
let key = this.options.keys[j]
if (typeof key !== 'string') {
weights[key.name] = {
weight: (1 - key.weight) || 1
weight: 1 - key.weight || 1
}
if (key.weight <= 0 || key.weight > 1) {
throw new Error('Key weight has to be > 0 and <= 1')
@ -164,24 +167,30 @@ class Fuse {
}
}
this._analyze({
this._analyze(
{
key,
value: this.options.getFn(item, key),
record: item,
index: i
}, {
},
{
resultMap,
results,
tokenSearchers,
fullSearcher
})
}
)
}
}
return { weights, results }
}
_analyze ({ key, arrayIndex = -1, value, record, index }, { tokenSearchers = [], fullSearcher = [], resultMap = {}, results = [] }) {
_analyze(
{ key, arrayIndex = -1, value, record, index },
{ tokenSearchers = [], fullSearcher = [], resultMap = {}, results = [] }
) {
// Check if the texvaluet can be searched
if (value === undefined || value === null) {
return
@ -250,7 +259,8 @@ class Fuse {
this._log('Score average:', finalScore)
let checkTextMatches = (this.options.tokenize && this.options.matchAllTokens) ? numTextMatches >= tokenSearchers.length : true
let checkTextMatches =
this.options.tokenize && this.options.matchAllTokens ? numTextMatches >= tokenSearchers.length : true
this._log(`\nCheck Matches: ${checkTextMatches}`)
@ -272,13 +282,15 @@ class Fuse {
// Add it to the raw result list
resultMap[index] = {
item: record,
output: [{
output: [
{
key,
arrayIndex,
value,
score: finalScore,
matchedIndices: mainSearchResult.matchedIndices
}]
}
]
}
results.push(resultMap[index])
@ -286,23 +298,26 @@ class Fuse {
}
} else if (isArray(value)) {
for (let i = 0, len = value.length; i < len; i += 1) {
this._analyze({
this._analyze(
{
key,
arrayIndex: i,
value: value[i],
record,
index
}, {
},
{
resultMap,
results,
tokenSearchers,
fullSearcher
})
}
)
}
}
}
_computeScore (weights, results) {
_computeScore(weights, results) {
this._log('\n\nComputing score:\n')
for (let i = 0, len = results.length; i < len; i += 1) {
@ -314,7 +329,7 @@ class Fuse {
for (let j = 0; j < scoreLen; j += 1) {
let weight = weights ? weights[output[j].key].weight : 1
let score = weight === 1 ? output[j].score : (output[j].score || 0.001)
let score = weight === 1 ? output[j].score : output[j].score || 0.001
let nScore = score * weight
if (weight !== 1) {
@ -331,12 +346,12 @@ class Fuse {
}
}
_sort (results) {
_sort(results) {
this._log('\n\nSorting....')
results.sort(this.options.sortFn)
}
_format (results) {
_format(results) {
const finalOutput = []
if (this.options.verbose) {
@ -404,7 +419,7 @@ class Fuse {
return finalOutput
}
_log () {
_log() {
if (this.options.verbose) {
console.log(...arguments)
}

@ -1,12 +1,15 @@
export function retry(func, retryCount=5) {
export function retry(func, retryCount = 5) {
return new Promise((resolve, reject) => {
func().then((...args) => {
resolve(...args);
}, () => {
func().then(
(...args) => {
resolve(...args)
},
() => {
if (retryCount === 0) {
return reject();
return reject()
}
setTimeout(() => retry(func, retryCount - 1).then(resolve, reject), 50);
});
});
setTimeout(() => retry(func, retryCount - 1).then(resolve, reject), 50)
}
)
})
}

@ -1,24 +1,24 @@
export default function promisify(web3, methodName, ...args) {
return new Promise((resolve, reject) => {
if (!web3) {
reject(new Error('No Web3 object'));
return;
reject(new Error('No Web3 object'))
return
}
const method = web3.eth[methodName];
const method = web3.eth[methodName]
if (!method) {
reject(new Error(`Cannot find web3.eth.${methodName}`));
return;
reject(new Error(`Cannot find web3.eth.${methodName}`))
return
}
method(...args, (error, data) => {
if (error) {
reject(error);
return;
reject(error)
return
}
resolve(data);
resolve(data)
})
})
});
}

@ -1,17 +1,17 @@
import promisify from "./web3-promisfy";
import promisify from './web3-promisfy'
export function getBlockDeadline(web3, deadline) {
return new Promise(async (resolve, reject) => {
const blockNumber = await promisify(web3, 'getBlockNumber');
const blockNumber = await promisify(web3, 'getBlockNumber')
if (!blockNumber && blockNumber !== 0) {
return reject();
return reject()
}
const block = await promisify(web3, 'getBlock', blockNumber);
const block = await promisify(web3, 'getBlock', blockNumber)
if (!block) {
return reject();
return reject()
}
resolve(block.timestamp + deadline);
});
resolve(block.timestamp + deadline)
})
}

@ -1,7 +1,7 @@
import i18n from "i18next";
import Backend from 'i18next-xhr-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { reactI18nextModule } from "react-i18next";
import i18n from 'i18next'
import Backend from 'i18next-xhr-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
import { reactI18nextModule } from 'react-i18next'
const resources = {
loadPath: `./locales/{{lng}}.json`
@ -20,13 +20,13 @@ i18n
// for all options read: https://www.i18next.com/overview/configuration-options
.init({
backend: resources,
fallbackLng: "en",
fallbackLng: 'en',
keySeparator: false,
interpolation: {
escapeValue: false
}
});
})
export default i18n;
export default i18n

@ -1,26 +1,25 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import ReactGA from 'react-ga';
import './i18n';
import App from './pages/App';
import store from './store';
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import ReactGA from 'react-ga'
import './i18n'
import App from './pages/App'
import store from './store'
import './index.scss';
import './index.scss'
if (process.env.NODE_ENV === 'development') {
// ReactGA.initialize('UA-128182339-02');
} else {
ReactGA.initialize('UA-128182339-1');
ReactGA.initialize('UA-128182339-1')
}
ReactGA.pageview(window.location.pathname + window.location.search);
ReactGA.pageview(window.location.pathname + window.location.search)
window.addEventListener('load', function() {
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
});
</Provider>,
document.getElementById('root')
)
})

@ -1,10 +1,11 @@
@import url('https://rsms.me/inter/inter-ui.css');
@import "./variables.scss";
@import './variables.scss';
html, body {
html,
body {
margin: 0;
padding: 0;
font-family: "Inter UI", sans-serif;
font-family: 'Inter UI', sans-serif;
font-size: 16px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@ -22,7 +23,7 @@ html, body {
background-color: $white;
z-index: 100;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
@media only screen and (min-width : 768px) {
@media only screen and (min-width: 768px) {
justify-content: center;
align-items: center;
}
@ -40,13 +41,17 @@ html, body {
border: 1px solid transparent; /* Light grey */
border-top: 1px solid $royal-blue; /* Blue */
border-radius: 50%;
width: .75rem;
height: .75rem;
margin-right: .25rem;
width: 0.75rem;
height: 0.75rem;
margin-right: 0.25rem;
animation: spin 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

@ -2,17 +2,203 @@
/*
@asset(/libraries/qr-scanner/qr-scanner-worker.min.js) */
'use strict';export default class QrScanner{constructor(video,onDecode,canvasSize=QrScanner.DEFAULT_CANVAS_SIZE){this.$video=video;this.$canvas=document.createElement("canvas");this._onDecode=onDecode;this._active=false;this.$canvas.width=canvasSize;this.$canvas.height=canvasSize;this._sourceRect={x:0,y:0,width:canvasSize,height:canvasSize};this.$video.addEventListener("canplay",()=>this._updateSourceRect());this.$video.addEventListener("play",()=>{this._updateSourceRect();this._scanFrame()},false);
this._qrWorker=new Worker(QrScanner.WORKER_PATH)}_updateSourceRect(){const smallestDimension=Math.min(this.$video.videoWidth,this.$video.videoHeight);const sourceRectSize=Math.round(2/3*smallestDimension);this._sourceRect.width=this._sourceRect.height=sourceRectSize;this._sourceRect.x=(this.$video.videoWidth-sourceRectSize)/2;this._sourceRect.y=(this.$video.videoHeight-sourceRectSize)/2}_scanFrame(){if(this.$video.paused||this.$video.ended)return false;requestAnimationFrame(()=>{QrScanner.scanImage(this.$video,
this._sourceRect,this._qrWorker,this.$canvas,true).then(this._onDecode,(error)=>{if(error!=="QR code not found.")console.error(error)}).then(()=>this._scanFrame())})}_getCameraStream(facingMode,exact=false){const constraintsToTry=[{width:{min:1024}},{width:{min:768}},{}];if(facingMode){if(exact)facingMode={exact:facingMode};constraintsToTry.forEach((constraint)=>constraint.facingMode=facingMode)}return this._getMatchingCameraStream(constraintsToTry)}_getMatchingCameraStream(constraintsToTry){if(constraintsToTry.length===
0)return Promise.reject("Camera not found.");return navigator.mediaDevices.getUserMedia({video:constraintsToTry.shift()}).catch(()=>this._getMatchingCameraStream(constraintsToTry))}start(){if(this._active)return Promise.resolve();this._active=true;clearTimeout(this._offTimeout);let facingMode="environment";return this._getCameraStream("environment",true).catch(()=>{facingMode="user";return this._getCameraStream()}).then((stream)=>{this.$video.srcObject=stream;this._setVideoMirror(facingMode)}).catch((e)=>
{this._active=false;throw e;})}stop(){if(!this._active)return;this._active=false;this.$video.pause();this._offTimeout=setTimeout(()=>{this.$video.srcObject.getTracks()[0].stop();this.$video.srcObject=null},3E3)}_setVideoMirror(facingMode){const scaleFactor=facingMode==="user"?-1:1;this.$video.style.transform="scaleX("+scaleFactor+")"}setGrayscaleWeights(red,green,blue){this._qrWorker.postMessage({type:"grayscaleWeights",data:{red,green,blue}})}static scanImage(imageOrFileOrUrl,sourceRect=null,worker=
null,canvas=null,fixedCanvasSize=false,alsoTryWithoutSourceRect=false){const promise=new Promise((resolve,reject)=>{worker=worker||new Worker(QrScanner.WORKER_PATH);let timeout,onMessage,onError;onMessage=(event)=>{if(event.data.type!=="qrResult")return;worker.removeEventListener("message",onMessage);worker.removeEventListener("error",onError);clearTimeout(timeout);if(event.data.data!==null)resolve(event.data.data);else reject("QR code not found.")};onError=()=>{worker.removeEventListener("message",
onMessage);worker.removeEventListener("error",onError);clearTimeout(timeout);reject("Worker error.")};worker.addEventListener("message",onMessage);worker.addEventListener("error",onError);timeout=setTimeout(onError,3E3);QrScanner._loadImage(imageOrFileOrUrl).then((image)=>{const imageData=QrScanner._getImageData(image,sourceRect,canvas,fixedCanvasSize);worker.postMessage({type:"decode",data:imageData},[imageData.data.buffer])}).catch(reject)});if(sourceRect&&alsoTryWithoutSourceRect)return promise.catch(()=>
QrScanner.scanImage(imageOrFileOrUrl,null,worker,canvas,fixedCanvasSize));else return promise}static _getImageData(image,sourceRect=null,canvas=null,fixedCanvasSize=false){canvas=canvas||document.createElement("canvas");const sourceRectX=sourceRect&&sourceRect.x?sourceRect.x:0;const sourceRectY=sourceRect&&sourceRect.y?sourceRect.y:0;const sourceRectWidth=sourceRect&&sourceRect.width?sourceRect.width:image.width||image.videoWidth;const sourceRectHeight=sourceRect&&sourceRect.height?sourceRect.height:
image.height||image.videoHeight;if(!fixedCanvasSize&&(canvas.width!==sourceRectWidth||canvas.height!==sourceRectHeight)){canvas.width=sourceRectWidth;canvas.height=sourceRectHeight}const context=canvas.getContext("2d",{alpha:false});context.imageSmoothingEnabled=false;context.drawImage(image,sourceRectX,sourceRectY,sourceRectWidth,sourceRectHeight,0,0,canvas.width,canvas.height);return context.getImageData(0,0,canvas.width,canvas.height)}static _loadImage(imageOrFileOrUrl){if(imageOrFileOrUrl instanceof
HTMLCanvasElement||imageOrFileOrUrl instanceof HTMLVideoElement||window.ImageBitmap&&imageOrFileOrUrl instanceof window.ImageBitmap||window.OffscreenCanvas&&imageOrFileOrUrl instanceof window.OffscreenCanvas)return Promise.resolve(imageOrFileOrUrl);else if(imageOrFileOrUrl instanceof Image)return QrScanner._awaitImageLoad(imageOrFileOrUrl).then(()=>imageOrFileOrUrl);else if(imageOrFileOrUrl instanceof File||imageOrFileOrUrl instanceof URL||typeof imageOrFileOrUrl==="string"){const image=new Image;
if(imageOrFileOrUrl instanceof File)image.src=URL.createObjectURL(imageOrFileOrUrl);else image.src=imageOrFileOrUrl;return QrScanner._awaitImageLoad(image).then(()=>{if(imageOrFileOrUrl instanceof File)URL.revokeObjectURL(image.src);return image})}else return Promise.reject("Unsupported image type.")}static _awaitImageLoad(image){return new Promise((resolve,reject)=>{if(image.complete&&image.naturalWidth!==0)resolve();else{let onLoad,onError;onLoad=()=>{image.removeEventListener("load",onLoad);image.removeEventListener("error",
onError);resolve()};onError=()=>{image.removeEventListener("load",onLoad);image.removeEventListener("error",onError);reject("Image load error")};image.addEventListener("load",onLoad);image.addEventListener("error",onError)}})}}QrScanner.DEFAULT_CANVAS_SIZE=400;QrScanner.WORKER_PATH="/libraries/qr-scanner/qr-scanner-worker.min.js";
'use strict'
export default class QrScanner {
constructor(video, onDecode, canvasSize = QrScanner.DEFAULT_CANVAS_SIZE) {
this.$video = video
this.$canvas = document.createElement('canvas')
this._onDecode = onDecode
this._active = false
this.$canvas.width = canvasSize
this.$canvas.height = canvasSize
this._sourceRect = { x: 0, y: 0, width: canvasSize, height: canvasSize }
this.$video.addEventListener('canplay', () => this._updateSourceRect())
this.$video.addEventListener(
'play',
() => {
this._updateSourceRect()
this._scanFrame()
},
false
)
this._qrWorker = new Worker(QrScanner.WORKER_PATH)
}
_updateSourceRect() {
const smallestDimension = Math.min(this.$video.videoWidth, this.$video.videoHeight)
const sourceRectSize = Math.round((2 / 3) * smallestDimension)
this._sourceRect.width = this._sourceRect.height = sourceRectSize
this._sourceRect.x = (this.$video.videoWidth - sourceRectSize) / 2
this._sourceRect.y = (this.$video.videoHeight - sourceRectSize) / 2
}
_scanFrame() {
if (this.$video.paused || this.$video.ended) return false
requestAnimationFrame(() => {
QrScanner.scanImage(this.$video, this._sourceRect, this._qrWorker, this.$canvas, true)
.then(this._onDecode, error => {
if (error !== 'QR code not found.') console.error(error)
})
.then(() => this._scanFrame())
})
}
_getCameraStream(facingMode, exact = false) {
const constraintsToTry = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}]
if (facingMode) {
if (exact) facingMode = { exact: facingMode }
constraintsToTry.forEach(constraint => (constraint.facingMode = facingMode))
}
return this._getMatchingCameraStream(constraintsToTry)
}
_getMatchingCameraStream(constraintsToTry) {
if (constraintsToTry.length === 0) return Promise.reject('Camera not found.')
return navigator.mediaDevices
.getUserMedia({ video: constraintsToTry.shift() })
.catch(() => this._getMatchingCameraStream(constraintsToTry))
}
start() {
if (this._active) return Promise.resolve()
this._active = true
clearTimeout(this._offTimeout)
let facingMode = 'environment'
return this._getCameraStream('environment', true)
.catch(() => {
facingMode = 'user'
return this._getCameraStream()
})
.then(stream => {
this.$video.srcObject = stream
this._setVideoMirror(facingMode)
})
.catch(e => {
this._active = false
throw e
})
}
stop() {
if (!this._active) return
this._active = false
this.$video.pause()
this._offTimeout = setTimeout(() => {
this.$video.srcObject.getTracks()[0].stop()
this.$video.srcObject = null
}, 3e3)
}
_setVideoMirror(facingMode) {
const scaleFactor = facingMode === 'user' ? -1 : 1
this.$video.style.transform = 'scaleX(' + scaleFactor + ')'
}
setGrayscaleWeights(red, green, blue) {
this._qrWorker.postMessage({ type: 'grayscaleWeights', data: { red, green, blue } })
}
static scanImage(
imageOrFileOrUrl,
sourceRect = null,
worker = null,
canvas = null,
fixedCanvasSize = false,
alsoTryWithoutSourceRect = false
) {
const promise = new Promise((resolve, reject) => {
worker = worker || new Worker(QrScanner.WORKER_PATH)
let timeout, onMessage, onError
onMessage = event => {
if (event.data.type !== 'qrResult') return
worker.removeEventListener('message', onMessage)
worker.removeEventListener('error', onError)
clearTimeout(timeout)
if (event.data.data !== null) resolve(event.data.data)
else reject('QR code not found.')
}
onError = () => {
worker.removeEventListener('message', onMessage)
worker.removeEventListener('error', onError)
clearTimeout(timeout)
reject('Worker error.')
}
worker.addEventListener('message', onMessage)
worker.addEventListener('error', onError)
timeout = setTimeout(onError, 3e3)
QrScanner._loadImage(imageOrFileOrUrl)
.then(image => {
const imageData = QrScanner._getImageData(image, sourceRect, canvas, fixedCanvasSize)
worker.postMessage({ type: 'decode', data: imageData }, [imageData.data.buffer])
})
.catch(reject)
})
if (sourceRect && alsoTryWithoutSourceRect)
return promise.catch(() => QrScanner.scanImage(imageOrFileOrUrl, null, worker, canvas, fixedCanvasSize))
else return promise
}
static _getImageData(image, sourceRect = null, canvas = null, fixedCanvasSize = false) {
canvas = canvas || document.createElement('canvas')
const sourceRectX = sourceRect && sourceRect.x ? sourceRect.x : 0
const sourceRectY = sourceRect && sourceRect.y ? sourceRect.y : 0
const sourceRectWidth = sourceRect && sourceRect.width ? sourceRect.width : image.width || image.videoWidth
const sourceRectHeight = sourceRect && sourceRect.height ? sourceRect.height : image.height || image.videoHeight
if (!fixedCanvasSize && (canvas.width !== sourceRectWidth || canvas.height !== sourceRectHeight)) {
canvas.width = sourceRectWidth
canvas.height = sourceRectHeight
}
const context = canvas.getContext('2d', { alpha: false })
context.imageSmoothingEnabled = false
context.drawImage(
image,
sourceRectX,
sourceRectY,
sourceRectWidth,
sourceRectHeight,
0,
0,
canvas.width,
canvas.height
)
return context.getImageData(0, 0, canvas.width, canvas.height)
}
static _loadImage(imageOrFileOrUrl) {
if (
imageOrFileOrUrl instanceof HTMLCanvasElement ||
imageOrFileOrUrl instanceof HTMLVideoElement ||
(window.ImageBitmap && imageOrFileOrUrl instanceof window.ImageBitmap) ||
(window.OffscreenCanvas && imageOrFileOrUrl instanceof window.OffscreenCanvas)
)
return Promise.resolve(imageOrFileOrUrl)
else if (imageOrFileOrUrl instanceof Image)
return QrScanner._awaitImageLoad(imageOrFileOrUrl).then(() => imageOrFileOrUrl)
else if (
imageOrFileOrUrl instanceof File ||
imageOrFileOrUrl instanceof URL ||
typeof imageOrFileOrUrl === 'string'
) {
const image = new Image()
if (imageOrFileOrUrl instanceof File) image.src = URL.createObjectURL(imageOrFileOrUrl)
else image.src = imageOrFileOrUrl
return QrScanner._awaitImageLoad(image).then(() => {
if (imageOrFileOrUrl instanceof File) URL.revokeObjectURL(image.src)
return image
})
} else return Promise.reject('Unsupported image type.')
}
static _awaitImageLoad(image) {
return new Promise((resolve, reject) => {
if (image.complete && image.naturalWidth !== 0) resolve()
else {
let onLoad, onError
onLoad = () => {
image.removeEventListener('load', onLoad)
image.removeEventListener('error', onError)
resolve()
}
onError = () => {
image.removeEventListener('load', onLoad)
image.removeEventListener('error', onError)
reject('Image load error')
}
image.addEventListener('load', onLoad)
image.addEventListener('error', onError)
}
})
}
}
QrScanner.DEFAULT_CANVAS_SIZE = 400
QrScanner.WORKER_PATH = '/libraries/qr-scanner/qr-scanner-worker.min.js'
//# sourceMappingURL=qr-scanner.min.js.map

@ -1,40 +1,40 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
import MediaQuery from 'react-responsive';
import { Web3Connect, startWatching, initialize } from '../ducks/web3connect';
import { setAddresses } from '../ducks/addresses';
import Header from '../components/Header';
import Swap from './Swap';
import Send from './Send';
import Pool from './Pool';
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
import MediaQuery from 'react-responsive'
import { Web3Connect, startWatching, initialize } from '../ducks/web3connect'
import { setAddresses } from '../ducks/addresses'
import Header from '../components/Header'
import Swap from './Swap'
import Send from './Send'
import Pool from './Pool'
import './App.scss';
import './App.scss'
class App extends Component {
componentWillMount() {
const { initialize, startWatching} = this.props;
initialize().then(startWatching);
};
const { initialize, startWatching } = this.props
initialize().then(startWatching)
}
componentWillUpdate() {
const { web3, setAddresses } = this.props;
const { web3, setAddresses } = this.props
if (this.hasSetNetworkId || !web3 || !web3.eth || !web3.eth.net || !web3.eth.net.getId) {
return;
return
}
web3.eth.net.getId((err, networkId) => {
if (!err && !this.hasSetNetworkId) {
setAddresses(networkId);
this.hasSetNetworkId = true;
setAddresses(networkId)
this.hasSetNetworkId = true
}
});
})
}
render() {
if (!this.props.initialized) {
return <noscript />;
return <noscript />
}
return (
@ -56,7 +56,7 @@ class App extends Component {
</Switch>
</BrowserRouter>
</div>
);
)
}
}
@ -64,11 +64,11 @@ export default connect(
state => ({
account: state.web3connect.account,
initialized: state.web3connect.initialized,
web3: state.web3connect.web3,
web3: state.web3connect.web3
}),
dispatch => ({
setAddresses: networkId => dispatch(setAddresses(networkId)),
initialize: () => dispatch(initialize()),
startWatching: () => dispatch(startWatching()),
}),
)(App);
startWatching: () => dispatch(startWatching())
})
)(App)

@ -1,4 +1,4 @@
@import "../variables.scss";
@import '../variables.scss';
.app {
&__wrapper {

@ -1,9 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
const div = document.createElement('div')
ReactDOM.render(<App />, div)
ReactDOM.unmountComponentAtNode(div)
})

@ -1,25 +1,25 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import classnames from "classnames";
import { withNamespaces } from 'react-i18next';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel';
import ContextualInfo from '../../components/ContextualInfo';
import NavigationTabs from '../../components/NavigationTabs';
import { selectors, addPendingTx } from '../../ducks/web3connect';
import PlusBlue from '../../assets/images/plus-blue.svg';
import PlusGrey from '../../assets/images/plus-grey.svg';
import { getBlockDeadline } from '../../helpers/web3-utils';
import { retry } from '../../helpers/promise-utils';
import ModeSelector from './ModeSelector';
import {BigNumber as BN} from 'bignumber.js';
import EXCHANGE_ABI from '../../abi/exchange';
import "./pool.scss";
import ReactGA from "react-ga";
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { withNamespaces } from 'react-i18next'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import OversizedPanel from '../../components/OversizedPanel'
import ContextualInfo from '../../components/ContextualInfo'
import NavigationTabs from '../../components/NavigationTabs'
import { selectors, addPendingTx } from '../../ducks/web3connect'
import PlusBlue from '../../assets/images/plus-blue.svg'
import PlusGrey from '../../assets/images/plus-grey.svg'
import { getBlockDeadline } from '../../helpers/web3-utils'
import { retry } from '../../helpers/promise-utils'
import ModeSelector from './ModeSelector'
import { BigNumber as BN } from 'bignumber.js'
import EXCHANGE_ABI from '../../abi/exchange'
import './pool.scss'
import ReactGA from 'react-ga'
const INPUT = 0;
const OUTPUT = 1;
const INPUT = 0
const OUTPUT = 1
class AddLiquidity extends Component {
static propTypes = {
@ -28,9 +28,9 @@ class AddLiquidity extends Component {
selectors: PropTypes.func.isRequired,
balances: PropTypes.object.isRequired,
exchangeAddresses: PropTypes.shape({
fromToken: PropTypes.object.isRequired,
}).isRequired,
};
fromToken: PropTypes.object.isRequired
}).isRequired
}
state = {
inputValue: '',
@ -38,22 +38,23 @@ class AddLiquidity extends Component {
inputCurrency: 'ETH',
outputCurrency: '',
lastEditedField: '',
totalSupply: BN(0),
};
totalSupply: BN(0)
}
reset = () => {
this.setState({
inputValue: '',
outputValue: '',
lastEditedField: '',
});
};
lastEditedField: ''
})
}
shouldComponentUpdate(nextProps, nextState) {
const { t, isConnected, account, exchangeAddresses, balances, web3 } = this.props;
const { inputValue, outputValue, inputCurrency, outputCurrency, lastEditedField } = this.state;
const { t, isConnected, account, exchangeAddresses, balances, web3 } = this.props
const { inputValue, outputValue, inputCurrency, outputCurrency, lastEditedField } = this.state
return isConnected !== nextProps.isConnected ||
return (
isConnected !== nextProps.isConnected ||
t !== nextProps.t ||
account !== nextProps.account ||
exchangeAddresses !== nextProps.exchangeAddresses ||
@ -63,308 +64,337 @@ class AddLiquidity extends Component {
outputValue !== nextState.outputValue ||
inputCurrency !== nextState.inputCurrency ||
outputCurrency !== nextState.outputCurrency ||
lastEditedField !== nextState.lastEditedField;
lastEditedField !== nextState.lastEditedField
)
}
componentWillReceiveProps() {
this.recalcForm();
this.recalcForm()
}
recalcForm = async () => {
const { outputCurrency, inputValue, outputValue, lastEditedField, totalSupply: oldTotalSupply } = this.state
const {
outputCurrency,
inputValue,
outputValue,
lastEditedField,
totalSupply: oldTotalSupply,
} = this.state;
const { exchangeAddresses: { fromToken }, web3 } = this.props;
const exchangeAddress = fromToken[outputCurrency];
const exchangeRate = this.getExchangeRate();
const append = {};
exchangeAddresses: { fromToken },
web3
} = this.props
const exchangeAddress = fromToken[outputCurrency]
const exchangeRate = this.getExchangeRate()
const append = {}
if (!outputCurrency || this.isNewExchange() || !web3) {
return;
return
}
const exchange = new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress);
const totalSupply = await exchange.methods.totalSupply().call();
const exchange = new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress)
const totalSupply = await exchange.methods.totalSupply().call()
if (!oldTotalSupply.isEqualTo(BN(totalSupply))) {
append.totalSupply = BN(totalSupply);
append.totalSupply = BN(totalSupply)
}
if (lastEditedField === INPUT) {
const newOutputValue = exchangeRate.multipliedBy(inputValue).toFixed(7);
const newOutputValue = exchangeRate.multipliedBy(inputValue).toFixed(7)
if (newOutputValue !== outputValue) {
append.outputValue = newOutputValue;
append.outputValue = newOutputValue
}
}
if (lastEditedField === OUTPUT) {
const newInputValue = BN(outputValue).dividedBy(exchangeRate).toFixed(7);
const newInputValue = BN(outputValue)
.dividedBy(exchangeRate)
.toFixed(7)
if (newInputValue !== inputValue) {
append.inputValue = newInputValue;
append.inputValue = newInputValue
}
}
this.setState(append);
};
this.setState(append)
}
getBalance(currency) {
const { t, selectors, account } = this.props;
const { t, selectors, account } = this.props
if (!currency) {
return '';
return ''
}
const { value, decimals } = selectors().getBalance(account, currency);
const { value, decimals } = selectors().getBalance(account, currency)
if (!decimals) {
return '';
return ''
}
const balanceInput = value.dividedBy(10 ** decimals).toFixed(4);
return t("balance", { balanceInput });
const balanceInput = value.dividedBy(10 ** decimals).toFixed(4)
return t('balance', { balanceInput })
}
isUnapproved() {
const { account, exchangeAddresses, selectors } = this.props;
const { outputCurrency, outputValue } = this.state;
const { account, exchangeAddresses, selectors } = this.props
const { outputCurrency, outputValue } = this.state
if (!outputCurrency) {
return false;
return false
}
const { value: allowance, label, decimals } = selectors().getApprovals(
outputCurrency,
account,
exchangeAddresses.fromToken[outputCurrency]
);
)
if (label && allowance.isLessThan(BN(outputValue * 10 ** decimals || 0))) {
return true;
return true
}
return false;
return false
}
onAddLiquidity = async () => {
const { account, web3, exchangeAddresses: { fromToken }, selectors } = this.props;
const { inputValue, outputValue, outputCurrency } = this.state;
const exchange = new web3.eth.Contract(EXCHANGE_ABI, fromToken[outputCurrency]);
const {
account,
web3,
exchangeAddresses: { fromToken },
selectors
} = this.props
const { inputValue, outputValue, outputCurrency } = this.state
const exchange = new web3.eth.Contract(EXCHANGE_ABI, fromToken[outputCurrency])
const ethAmount = BN(inputValue).multipliedBy(10 ** 18);
const { decimals } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency]);
const tokenAmount = BN(outputValue).multipliedBy(10 ** decimals);
const { value: ethReserve } = selectors().getBalance(fromToken[outputCurrency]);
const totalLiquidity = await exchange.methods.totalSupply().call();
const liquidityMinted = BN(totalLiquidity).multipliedBy(ethAmount.dividedBy(ethReserve));
let deadline;
const ethAmount = BN(inputValue).multipliedBy(10 ** 18)
const { decimals } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency])
const tokenAmount = BN(outputValue).multipliedBy(10 ** decimals)
const { value: ethReserve } = selectors().getBalance(fromToken[outputCurrency])
const totalLiquidity = await exchange.methods.totalSupply().call()
const liquidityMinted = BN(totalLiquidity).multipliedBy(ethAmount.dividedBy(ethReserve))
let deadline
try {
deadline = await retry(() => getBlockDeadline(web3, 600));
} catch(e) {
deadline = await retry(() => getBlockDeadline(web3, 600))
} catch (e) {
// TODO: Handle error.
return;
return
}
const MAX_LIQUIDITY_SLIPPAGE = 0.025;
const minLiquidity = this.isNewExchange() ? BN(0) : liquidityMinted.multipliedBy(1 - MAX_LIQUIDITY_SLIPPAGE);
const maxTokens = this.isNewExchange() ? tokenAmount : tokenAmount.multipliedBy(1 + MAX_LIQUIDITY_SLIPPAGE);
const MAX_LIQUIDITY_SLIPPAGE = 0.025
const minLiquidity = this.isNewExchange() ? BN(0) : liquidityMinted.multipliedBy(1 - MAX_LIQUIDITY_SLIPPAGE)
const maxTokens = this.isNewExchange() ? tokenAmount : tokenAmount.multipliedBy(1 + MAX_LIQUIDITY_SLIPPAGE)
try {
exchange.methods.addLiquidity(minLiquidity.toFixed(0), maxTokens.toFixed(0), deadline).send({
exchange.methods.addLiquidity(minLiquidity.toFixed(0), maxTokens.toFixed(0), deadline).send(
{
from: account,
value: ethAmount.toFixed(0)
}, (err, data) => {
this.reset();
this.props.addPendingTx(data);
},
(err, data) => {
this.reset()
this.props.addPendingTx(data)
if (data) {
ReactGA.event({
category: 'Pool',
action: 'AddLiquidity',
});
action: 'AddLiquidity'
})
}
});
}
)
} catch (err) {
console.error(err);
console.error(err)
}
}
};
onInputChange = value => {
const { inputCurrency, outputCurrency } = this.state;
const exchangeRate = this.getExchangeRate();
let outputValue;
const { inputCurrency, outputCurrency } = this.state
const exchangeRate = this.getExchangeRate()
let outputValue
if (inputCurrency === 'ETH' && outputCurrency && outputCurrency !== 'ETH') {
outputValue = exchangeRate.multipliedBy(value).toFixed(7);
outputValue = exchangeRate.multipliedBy(value).toFixed(7)
}
if (outputCurrency === 'ETH' && inputCurrency && inputCurrency !== 'ETH') {
outputValue = BN(value).dividedBy(exchangeRate).toFixed(7);
outputValue = BN(value)
.dividedBy(exchangeRate)
.toFixed(7)
}
const append = {
inputValue: value,
lastEditedField: INPUT,
};
if (!this.isNewExchange()) {
append.outputValue = outputValue;
lastEditedField: INPUT
}
this.setState(append);
};
if (!this.isNewExchange()) {
append.outputValue = outputValue
}
this.setState(append)
}
onOutputChange = value => {
const { inputCurrency, outputCurrency } = this.state;
const exchangeRate = this.getExchangeRate();
let inputValue;
const { inputCurrency, outputCurrency } = this.state
const exchangeRate = this.getExchangeRate()
let inputValue
if (inputCurrency === 'ETH' && outputCurrency && outputCurrency !== 'ETH') {
inputValue = BN(value).dividedBy(exchangeRate).toFixed(7);
inputValue = BN(value)
.dividedBy(exchangeRate)
.toFixed(7)
}
if (outputCurrency === 'ETH' && inputCurrency && inputCurrency !== 'ETH') {
inputValue = exchangeRate.multipliedBy(value).toFixed(7);
inputValue = exchangeRate.multipliedBy(value).toFixed(7)
}
const append = {
outputValue: value,
lastEditedField: INPUT,
};
lastEditedField: INPUT
}
if (!this.isNewExchange()) {
append.inputValue = inputValue;
append.inputValue = inputValue
}
this.setState(append);
};
this.setState(append)
}
isNewExchange() {
const { selectors, exchangeAddresses: { fromToken } } = this.props;
const { inputCurrency, outputCurrency } = this.state;
const eth = [inputCurrency, outputCurrency].filter(currency => currency === 'ETH')[0];
const token = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0];
const {
selectors,
exchangeAddresses: { fromToken }
} = this.props
const { inputCurrency, outputCurrency } = this.state
const eth = [inputCurrency, outputCurrency].filter(currency => currency === 'ETH')[0]
const token = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0]
if (!eth || !token) {
return false;
return false
}
const { value: tokenValue, decimals } = selectors().getBalance(fromToken[token], token);
const { value: ethValue } = selectors().getBalance(fromToken[token], eth);
const { value: tokenValue, decimals } = selectors().getBalance(fromToken[token], token)
const { value: ethValue } = selectors().getBalance(fromToken[token], eth)
return tokenValue.isZero() && ethValue.isZero() && decimals !== 0;
return tokenValue.isZero() && ethValue.isZero() && decimals !== 0
}
getExchangeRate() {
const { selectors, exchangeAddresses: { fromToken } } = this.props;
const { inputCurrency, outputCurrency } = this.state;
const eth = [inputCurrency, outputCurrency].filter(currency => currency === 'ETH')[0];
const token = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0];
const {
selectors,
exchangeAddresses: { fromToken }
} = this.props
const { inputCurrency, outputCurrency } = this.state
const eth = [inputCurrency, outputCurrency].filter(currency => currency === 'ETH')[0]
const token = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0]
if (!eth || !token) {
return;
return
}
const { value: tokenValue, decimals } = selectors().getBalance(fromToken[token], token);
const { value: ethValue } = selectors().getBalance(fromToken[token], eth);
const { value: tokenValue, decimals } = selectors().getBalance(fromToken[token], token)
const { value: ethValue } = selectors().getBalance(fromToken[token], eth)
return tokenValue.multipliedBy(10 ** (18 - decimals)).dividedBy(ethValue);
return tokenValue.multipliedBy(10 ** (18 - decimals)).dividedBy(ethValue)
}
validate() {
const { t, selectors, account } = this.props;
const {
inputValue, outputValue,
inputCurrency, outputCurrency,
} = this.state;
const { t, selectors, account } = this.props
const { inputValue, outputValue, inputCurrency, outputCurrency } = this.state
let inputError;
let outputError;
let isValid = true;
const inputIsZero = BN(inputValue).isZero();
const outputIsZero = BN(outputValue).isZero();
let inputError
let outputError
let isValid = true
const inputIsZero = BN(inputValue).isZero()
const outputIsZero = BN(outputValue).isZero()
if (!inputValue || inputIsZero || !outputValue || outputIsZero || !inputCurrency || !outputCurrency || this.isUnapproved()) {
isValid = false;
if (
!inputValue ||
inputIsZero ||
!outputValue ||
outputIsZero ||
!inputCurrency ||
!outputCurrency ||
this.isUnapproved()
) {
isValid = false
}
const { value: ethValue } = selectors().getBalance(account, inputCurrency);
const { value: tokenValue, decimals } = selectors().getBalance(account, outputCurrency);
const { value: ethValue } = selectors().getBalance(account, inputCurrency)
const { value: tokenValue, decimals } = selectors().getBalance(account, outputCurrency)
if (ethValue.isLessThan(BN(inputValue * 10 ** 18))) {
inputError = t("insufficientBalance");
inputError = t('insufficientBalance')
}
if (tokenValue.isLessThan(BN(outputValue * 10 ** decimals))) {
outputError = t("insufficientBalance");
outputError = t('insufficientBalance')
}
return {
inputError,
outputError,
isValid: isValid && !inputError && !outputError,
};
isValid: isValid && !inputError && !outputError
}
}
renderInfo() {
const t = this.props.t;
const t = this.props.t
const blank = (
<div className="pool__summary-panel">
<div className="pool__exchange-rate-wrapper">
<span className="pool__exchange-rate">{t("exchangeRate")}</span>
<span className="pool__exchange-rate">{t('exchangeRate')}</span>
<span> - </span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">{t("currentPoolSize")}</span>
<span className="swap__exchange-rate">{t('currentPoolSize')}</span>
<span> - </span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">{t("yourPoolShare")}</span>
<span className="swap__exchange-rate">{t('yourPoolShare')}</span>
<span> - </span>
</div>
</div>
);
)
const { selectors, exchangeAddresses: { fromToken }, account } = this.props;
const { getBalance } = selectors();
const { inputCurrency, outputCurrency, inputValue, outputValue, totalSupply } = this.state;
const eth = [inputCurrency, outputCurrency].filter(currency => currency === 'ETH')[0];
const token = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0];
const exchangeAddress = fromToken[token];
const {
selectors,
exchangeAddresses: { fromToken },
account
} = this.props
const { getBalance } = selectors()
const { inputCurrency, outputCurrency, inputValue, outputValue, totalSupply } = this.state
const eth = [inputCurrency, outputCurrency].filter(currency => currency === 'ETH')[0]
const token = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0]
const exchangeAddress = fromToken[token]
if (!eth || !token || !exchangeAddress) {
return blank;
return blank
}
const { value: tokenValue, decimals, label } = getBalance(exchangeAddress, token);
const { value: ethValue } = getBalance(exchangeAddress);
const { value: liquidityBalance } = getBalance(account, exchangeAddress);
const ownership = liquidityBalance.dividedBy(totalSupply);
const ethPer = ethValue.dividedBy(totalSupply);
const tokenPer = tokenValue.dividedBy(totalSupply);
const ownedEth = ethPer.multipliedBy(liquidityBalance).dividedBy(10 ** 18);
const ownedToken = tokenPer.multipliedBy(liquidityBalance).dividedBy(10 ** decimals);
const { value: tokenValue, decimals, label } = getBalance(exchangeAddress, token)
const { value: ethValue } = getBalance(exchangeAddress)
const { value: liquidityBalance } = getBalance(account, exchangeAddress)
const ownership = liquidityBalance.dividedBy(totalSupply)
const ethPer = ethValue.dividedBy(totalSupply)
const tokenPer = tokenValue.dividedBy(totalSupply)
const ownedEth = ethPer.multipliedBy(liquidityBalance).dividedBy(10 ** 18)
const ownedToken = tokenPer.multipliedBy(liquidityBalance).dividedBy(10 ** decimals)
if (!label || !decimals) {
return blank;
return blank
}
if (this.isNewExchange()) {
const rate = BN(outputValue).dividedBy(inputValue);
const rateText = rate.isNaN() ? '---' : rate.toFixed(4);
const rate = BN(outputValue).dividedBy(inputValue)
const rateText = rate.isNaN() ? '---' : rate.toFixed(4)
return (
<div className="pool__summary-panel">
<div className="pool__exchange-rate-wrapper">
<span className="pool__exchange-rate">{t("exchangeRate")}</span>
<span className="pool__exchange-rate">{t('exchangeRate')}</span>
<span>{`1 ETH = ${rateText} ${label}`}</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">{t("currentPoolSize")}</span>
<span>{` ${ethValue.dividedBy(10 ** 18).toFixed(2)} ${eth} + ${tokenValue.dividedBy(10 ** decimals).toFixed(2)} ${label}`}</span>
<span className="swap__exchange-rate">{t('currentPoolSize')}</span>
<span>{` ${ethValue.dividedBy(10 ** 18).toFixed(2)} ${eth} + ${tokenValue
.dividedBy(10 ** decimals)
.toFixed(2)} ${label}`}</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">
{t("yourPoolShare")} ({ownership.multipliedBy(100).toFixed(2)}%)
{t('yourPoolShare')} ({ownership.multipliedBy(100).toFixed(2)}%)
</span>
<span>{`${ownedEth.toFixed(2)} ETH + ${ownedToken.toFixed(2)} ${label}`}</span>
</div>
@ -373,22 +403,27 @@ class AddLiquidity extends Component {
}
if (tokenValue.dividedBy(ethValue).isNaN()) {
return blank;
return blank
}
return (
<div className="pool__summary-panel">
<div className="pool__exchange-rate-wrapper">
<span className="pool__exchange-rate">{t("exchangeRate")}</span>
<span>{`1 ETH = ${tokenValue.multipliedBy(10 ** (18 - decimals)).dividedBy(ethValue).toFixed(4)} ${label}`}</span>
<span className="pool__exchange-rate">{t('exchangeRate')}</span>
<span>{`1 ETH = ${tokenValue
.multipliedBy(10 ** (18 - decimals))
.dividedBy(ethValue)
.toFixed(4)} ${label}`}</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">{t("currentPoolSize")}</span>
<span>{` ${ethValue.dividedBy(10 ** 18).toFixed(2)} ${eth} + ${tokenValue.dividedBy(10 ** decimals).toFixed(2)} ${label}`}</span>
<span className="swap__exchange-rate">{t('currentPoolSize')}</span>
<span>{` ${ethValue.dividedBy(10 ** 18).toFixed(2)} ${eth} + ${tokenValue
.dividedBy(10 ** decimals)
.toFixed(2)} ${label}`}</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">
{t("yourPoolShare")} ({ownership.multipliedBy(100).toFixed(2)}%)
{t('yourPoolShare')} ({ownership.multipliedBy(100).toFixed(2)}%)
</span>
<span>{`${ownedEth.toFixed(2)} ETH + ${ownedToken.toFixed(2)} ${label}`}</span>
</div>
@ -397,93 +432,114 @@ class AddLiquidity extends Component {
}
renderSummary(inputError, outputError) {
const { t, selectors, exchangeAddresses: { fromToken } } = this.props;
const {
inputValue,
outputValue,
inputCurrency,
outputCurrency,
} = this.state;
const inputIsZero = BN(inputValue).isZero();
const outputIsZero = BN(outputValue).isZero();
t,
selectors,
exchangeAddresses: { fromToken }
} = this.props
const { inputValue, outputValue, inputCurrency, outputCurrency } = this.state
const inputIsZero = BN(inputValue).isZero()
const outputIsZero = BN(outputValue).isZero()
let contextualInfo = '';
let isError = false;
const { label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency]);
let contextualInfo = ''
let isError = false
const { label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency])
if (inputError || outputError) {
contextualInfo = inputError || outputError;
isError = true;
contextualInfo = inputError || outputError
isError = true
} else if (!inputCurrency || !outputCurrency) {
contextualInfo = t("selectTokenCont");
contextualInfo = t('selectTokenCont')
} else if (inputCurrency === outputCurrency) {
contextualInfo = t("differentToken");
contextualInfo = t('differentToken')
} else if (![inputCurrency, outputCurrency].includes('ETH')) {
contextualInfo = t("mustBeETH");
contextualInfo = t('mustBeETH')
} else if (inputIsZero || outputIsZero) {
contextualInfo = t("noZero");
contextualInfo = t('noZero')
} else if (this.isUnapproved()) {
contextualInfo = t("unlockTokenCont");
contextualInfo = t('unlockTokenCont')
} else if (!inputValue || !outputValue) {
contextualInfo = t("enterCurrencyOrLabelCont", {inputCurrency, label});
contextualInfo = t('enterCurrencyOrLabelCont', { inputCurrency, label })
}
return (
<ContextualInfo
key="context-info"
openDetailsText={t("transactionDetails")}
closeDetailsText={t("hideDetails")}
openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo}
isError={isError}
renderTransactionDetails={this.renderTransactionDetails}
/>
);
)
}
renderTransactionDetails = () => {
const { t, selectors, exchangeAddresses: { fromToken }, account } = this.props;
const {
inputValue,
outputValue,
outputCurrency,
totalSupply,
} = this.state;
t,
selectors,
exchangeAddresses: { fromToken },
account
} = this.props
const { inputValue, outputValue, outputCurrency, totalSupply } = this.state
ReactGA.event({
category: 'TransactionDetail',
action: 'Open',
});
action: 'Open'
})
const { value: tokenReserve, label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency]);
const { value: ethReserve } = selectors().getBalance(fromToken[outputCurrency]);
const { decimals: poolTokenDecimals } = selectors().getBalance(account, fromToken[outputCurrency]);
const { value: tokenReserve, label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency])
const { value: ethReserve } = selectors().getBalance(fromToken[outputCurrency])
const { decimals: poolTokenDecimals } = selectors().getBalance(account, fromToken[outputCurrency])
if (this.isNewExchange()) {
return (
<div>
<div className="pool__summary-item">{t("youAreAdding")} {b(`${inputValue} ETH`)} {t("and")} {b(`${outputValue} ${label}`)} {t("intoPool")}</div>
<div className="pool__summary-item">{t("youAreSettingExRate")} {b(`1 ETH = ${BN(outputValue).dividedBy(inputValue).toFixed(4)} ${label}`)}.</div>
<div className="pool__summary-item">{t("youWillMint")} {b(`${inputValue}`)} {t("liquidityTokens")}</div>
<div className="pool__summary-item">{t("totalSupplyIs0")}</div>
<div className="pool__summary-item">
{t('youAreAdding')} {b(`${inputValue} ETH`)} {t('and')} {b(`${outputValue} ${label}`)} {t('intoPool')}
</div>
);
<div className="pool__summary-item">
{t('youAreSettingExRate')}{' '}
{b(
`1 ETH = ${BN(outputValue)
.dividedBy(inputValue)
.toFixed(4)} ${label}`
)}
.
</div>
<div className="pool__summary-item">
{t('youWillMint')} {b(`${inputValue}`)} {t('liquidityTokens')}
</div>
<div className="pool__summary-item">{t('totalSupplyIs0')}</div>
</div>
)
}
const SLIPPAGE = 0.025;
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE);
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE);
const SLIPPAGE = 0.025
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE)
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE)
// const minPercentage = minOutput.dividedBy(minOutput.plus(tokenReserve)).multipliedBy(100);
// const maxPercentage = maxOutput.dividedBy(maxOutput.plus(tokenReserve)).multipliedBy(100);
const liquidityMinted = BN(inputValue).multipliedBy(totalSupply.dividedBy(ethReserve));
const adjTotalSupply = totalSupply.dividedBy(10 ** poolTokenDecimals);
const liquidityMinted = BN(inputValue).multipliedBy(totalSupply.dividedBy(ethReserve))
const adjTotalSupply = totalSupply.dividedBy(10 ** poolTokenDecimals)
return (
<div>
<div className="pool__summary-modal__item">{t("youAreAdding")} {b(`${+BN(inputValue).toFixed(7)} ETH`)} {t("and")} {b(`${+minOutput.toFixed(7)} - ${+maxOutput.toFixed(7)} ${label}`)} {t("intoPool")}</div>
<div className="pool__summary-modal__item">{t("youWillMint")} {b(+liquidityMinted.toFixed(7))} {t("liquidityTokens")}</div>
<div className="pool__summary-modal__item">{t("totalSupplyIs")} {b(+adjTotalSupply.toFixed(7))}</div>
<div className="pool__summary-modal__item">{t("tokenWorth")} {b(+ethReserve.dividedBy(totalSupply).toFixed(7))} ETH {t("and")} {b(+tokenReserve.dividedBy(totalSupply).toFixed(7))} {label}</div>
<div className="pool__summary-modal__item">
{t('youAreAdding')} {b(`${+BN(inputValue).toFixed(7)} ETH`)} {t('and')}{' '}
{b(`${+minOutput.toFixed(7)} - ${+maxOutput.toFixed(7)} ${label}`)} {t('intoPool')}
</div>
);
<div className="pool__summary-modal__item">
{t('youWillMint')} {b(+liquidityMinted.toFixed(7))} {t('liquidityTokens')}
</div>
<div className="pool__summary-modal__item">
{t('totalSupplyIs')} {b(+adjTotalSupply.toFixed(7))}
</div>
<div className="pool__summary-modal__item">
{t('tokenWorth')} {b(+ethReserve.dividedBy(totalSupply).toFixed(7))} ETH {t('and')}{' '}
{b(+tokenReserve.dividedBy(totalSupply).toFixed(7))} {label}
</div>
</div>
)
}
render() {
@ -491,49 +547,41 @@ class AddLiquidity extends Component {
t,
isConnected,
exchangeAddresses: { fromToken },
selectors,
} = this.props;
selectors
} = this.props
const {
inputValue,
outputValue,
inputCurrency,
outputCurrency,
} = this.state;
const { inputValue, outputValue, inputCurrency, outputCurrency } = this.state
const { inputError, outputError, isValid } = this.validate();
const { label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency]);
const { inputError, outputError, isValid } = this.validate()
const { label } = selectors().getTokenBalance(outputCurrency, fromToken[outputCurrency])
return [
<div
key="content"
className={classnames('swap__content', {
'swap--inactive': !isConnected,
'swap--inactive': !isConnected
})}
>
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !isConnected,
'header--inactive': !isConnected
})}
/>
{
this.isNewExchange()
? (
{this.isNewExchange() ? (
<div className="pool__new-exchange-warning">
<div className="pool__new-exchange-warning-text">
<span role='img' aria-label='liquidity'>🚰</span> {t("firstLiquidity")}
<span role="img" aria-label="liquidity">
🚰
</span>{' '}
{t('firstLiquidity')}
</div>
<div className="pool__new-exchange-warning-text">
{ t("initialExchangeRate", { label }) }
<div className="pool__new-exchange-warning-text">{t('initialExchangeRate', { label })}</div>
</div>
</div>
)
: null
}
<ModeSelector title={t("addLiquidity")}/>
) : null}
<ModeSelector title={t('addLiquidity')} />
<CurrencyInputPanel
title={t("deposit")}
title={t('deposit')}
extraText={this.getBalance(inputCurrency)}
onValueChange={this.onInputChange}
selectedTokenAddress="ETH"
@ -543,58 +591,60 @@ class AddLiquidity extends Component {
/>
<OversizedPanel>
<div className="swap__down-arrow-background">
<img className="swap__down-arrow" src={isValid ? PlusBlue : PlusGrey} alt='plus' />
<img className="swap__down-arrow" src={isValid ? PlusBlue : PlusGrey} alt="plus" />
</div>
</OversizedPanel>
<CurrencyInputPanel
title={t("deposit")}
description={this.isNewExchange() ? `(${t("estimated")})` : ''}
title={t('deposit')}
description={this.isNewExchange() ? `(${t('estimated')})` : ''}
extraText={this.getBalance(outputCurrency)}
selectedTokenAddress={outputCurrency}
onCurrencySelected={currency => {
this.setState({
outputCurrency: currency,
}, this.recalcForm);
this.setState(
{
outputCurrency: currency
},
this.recalcForm
)
}}
onValueChange={this.onOutputChange}
value={outputValue}
errorMessage={outputError}
filteredTokens={[ 'ETH' ]}
filteredTokens={['ETH']}
/>
<OversizedPanel hideBottom>
{ this.renderInfo() }
</OversizedPanel>
{ this.renderSummary(inputError, outputError) }
<OversizedPanel hideBottom>{this.renderInfo()}</OversizedPanel>
{this.renderSummary(inputError, outputError)}
<div className="pool__cta-container">
<button
className={classnames('pool__cta-btn', {
'swap--inactive': !this.props.isConnected,
'pool__cta-btn--inactive': !isValid,
'pool__cta-btn--inactive': !isValid
})}
disabled={!isValid}
onClick={this.onAddLiquidity}
>
{t("addLiquidity")}
{t('addLiquidity')}
</button>
</div>
</div>
];
]
}
}
export default connect(
state => ({
isConnected: Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID||1),
isConnected:
Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID || 1),
account: state.web3connect.account,
balances: state.web3connect.balances,
web3: state.web3connect.web3,
exchangeAddresses: state.addresses.exchangeAddresses,
exchangeAddresses: state.addresses.exchangeAddresses
}),
dispatch => ({
selectors: () => dispatch(selectors()),
addPendingTx: id => dispatch(addPendingTx(id)),
addPendingTx: id => dispatch(addPendingTx(id))
})
)(withNamespaces()(AddLiquidity));
)(withNamespaces()(AddLiquidity))
function b(text) {
return <span className="swap__highlight-text">{text}</span>

@ -1,17 +1,17 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { withNamespaces } from 'react-i18next';
import {selectors, addPendingTx} from "../../ducks/web3connect";
import classnames from "classnames";
import NavigationTabs from "../../components/NavigationTabs";
import ModeSelector from "./ModeSelector";
import AddressInputPanel from "../../components/AddressInputPanel";
import OversizedPanel from "../../components/OversizedPanel";
import FACTORY_ABI from "../../abi/factory";
import {addExchange} from "../../ducks/addresses";
import ReactGA from "react-ga";
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'
import { withNamespaces } from 'react-i18next'
import { selectors, addPendingTx } from '../../ducks/web3connect'
import classnames from 'classnames'
import NavigationTabs from '../../components/NavigationTabs'
import ModeSelector from './ModeSelector'
import AddressInputPanel from '../../components/AddressInputPanel'
import OversizedPanel from '../../components/OversizedPanel'
import FACTORY_ABI from '../../abi/factory'
import { addExchange } from '../../ducks/addresses'
import ReactGA from 'react-ga'
class CreateExchange extends Component {
static propTypes = {
@ -22,23 +22,27 @@ class CreateExchange extends Component {
isConnected: PropTypes.bool.isRequired,
factoryAddress: PropTypes.string.isRequired,
exchangeAddresses: PropTypes.shape({
fromToken: PropTypes.object.isRequired,
}).isRequired,
};
fromToken: PropTypes.object.isRequired
}).isRequired
}
constructor(props) {
super(props);
const { match: { params: { tokenAddress } } } = this.props;
super(props)
const {
match: {
params: { tokenAddress }
}
} = this.props
this.state = {
tokenAddress,
label: '',
decimals: 0,
};
decimals: 0
}
}
validate() {
const { tokenAddress } = this.state;
const { tokenAddress } = this.state
const {
t,
web3,
@ -46,104 +50,104 @@ class CreateExchange extends Component {
selectors,
factoryAddress,
exchangeAddresses: { fromToken },
addExchange,
} = this.props;
addExchange
} = this.props
let isValid = true;
let errorMessage = '';
let isValid = true
let errorMessage = ''
if (!tokenAddress) {
return {
isValid: false,
};
isValid: false
}
}
if (web3 && web3.utils && !web3.utils.isAddress(tokenAddress)) {
return {
isValid: false,
errorMessage: t("invalidTokenAddress"),
};
errorMessage: t('invalidTokenAddress')
}
}
const { label, decimals } = selectors().getBalance(account, tokenAddress);
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress);
const exchangeAddress = fromToken[tokenAddress];
const { label, decimals } = selectors().getBalance(account, tokenAddress)
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress)
const exchangeAddress = fromToken[tokenAddress]
if (!exchangeAddress) {
factory.methods.getExchange(tokenAddress).call((err, data) => {
if (!err && data !== '0x0000000000000000000000000000000000000000') {
addExchange({ label, tokenAddress, exchangeAddress: data });
addExchange({ label, tokenAddress, exchangeAddress: data })
}
});
})
} else {
errorMessage = t("exchangeExists", { label });
errorMessage = t('exchangeExists', { label })
}
if (!label) {
errorMessage = t("invalidSymbol");
errorMessage = t('invalidSymbol')
}
if (!decimals) {
errorMessage = t("invalidDecimals");
errorMessage = t('invalidDecimals')
}
return {
isValid: isValid && !errorMessage,
errorMessage,
};
errorMessage
}
}
onChange = tokenAddress => {
const { selectors, account, web3 } = this.props;
const { selectors, account, web3 } = this.props
if (web3 && web3.utils && web3.utils.isAddress(tokenAddress)) {
const { label, decimals } = selectors().getBalance(account, tokenAddress);
const { label, decimals } = selectors().getBalance(account, tokenAddress)
this.setState({
label,
decimals,
tokenAddress,
});
tokenAddress
})
} else {
this.setState({
label: '',
decimals: 0,
tokenAddress,
});
tokenAddress
})
}
}
};
onCreateExchange = () => {
const { tokenAddress } = this.state;
const { account, web3, factoryAddress } = this.props;
const { tokenAddress } = this.state
const { account, web3, factoryAddress } = this.props
if (web3 && web3.utils && !web3.utils.isAddress(tokenAddress)) {
return;
return
}
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress);
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress)
factory.methods.createExchange(tokenAddress).send({ from: account }, (err, data) => {
if (!err) {
this.setState({
label: '',
decimals: 0,
tokenAddress: '',
});
this.props.addPendingTx(data);
tokenAddress: ''
})
this.props.addPendingTx(data)
ReactGA.event({
category: 'Pool',
action: 'CreateExchange',
});
action: 'CreateExchange'
})
}
})
};
}
renderSummary() {
const { tokenAddress } = this.state;
const { errorMessage } = this.validate();
const { tokenAddress } = this.state
const { errorMessage } = this.validate()
if (!tokenAddress) {
return (
<div className="create-exchange__summary-panel">
<div className="create-exchange__summary-text">{this.props.t("enterTokenCont")}</div>
<div className="create-exchange__summary-text">{this.props.t('enterTokenCont')}</div>
</div>
)
}
@ -156,36 +160,36 @@ class CreateExchange extends Component {
)
}
return null;
return null
}
render() {
const { tokenAddress } = this.state;
const { t, isConnected, account, selectors, web3 } = this.props;
const { isValid, errorMessage } = this.validate();
let label, decimals;
const { tokenAddress } = this.state
const { t, isConnected, account, selectors, web3 } = this.props
const { isValid, errorMessage } = this.validate()
let label, decimals
if (web3 && web3.utils && web3.utils.isAddress(tokenAddress)) {
const { label: _label, decimals: _decimals } = selectors().getBalance(account, tokenAddress);
label = _label;
decimals = _decimals;
const { label: _label, decimals: _decimals } = selectors().getBalance(account, tokenAddress)
label = _label
decimals = _decimals
}
return (
<div
key="content"
className={classnames('swap__content', {
'swap--inactive': !isConnected,
'swap--inactive': !isConnected
})}
>
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !isConnected,
'header--inactive': !isConnected
})}
/>
<ModeSelector title={t("createExchange")} />
<ModeSelector title={t('createExchange')} />
<AddressInputPanel
title={t("tokenAddress")}
title={t('tokenAddress')}
value={tokenAddress}
onChange={this.onChange}
errorMessage={errorMessage}
@ -193,46 +197,47 @@ class CreateExchange extends Component {
<OversizedPanel hideBottom>
<div className="pool__summary-panel">
<div className="pool__exchange-rate-wrapper">
<span className="pool__exchange-rate">{t("label")}</span>
<span className="pool__exchange-rate">{t('label')}</span>
<span>{label || ' - '}</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">{t("decimals")}</span>
<span className="swap__exchange-rate">{t('decimals')}</span>
<span>{decimals || ' - '}</span>
</div>
</div>
</OversizedPanel>
{ this.renderSummary() }
{this.renderSummary()}
<div className="pool__cta-container">
<button
className={classnames('pool__cta-btn', {
'swap--inactive': !isConnected,
'swap--inactive': !isConnected
})}
disabled={!isValid}
onClick={this.onCreateExchange}
>
{t("createExchange")}
{t('createExchange')}
</button>
</div>
</div>
);
)
}
}
export default withRouter(
connect(
state => ({
isConnected: Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID||1),
isConnected:
Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID || 1),
account: state.web3connect.account,
balances: state.web3connect.balances,
web3: state.web3connect.web3,
exchangeAddresses: state.addresses.exchangeAddresses,
factoryAddress: state.addresses.factoryAddress,
factoryAddress: state.addresses.factoryAddress
}),
dispatch => ({
selectors: () => dispatch(selectors()),
addExchange: opts => dispatch(addExchange(opts)),
addPendingTx: id => dispatch(addPendingTx(id)),
addPendingTx: id => dispatch(addPendingTx(id))
})
)(withNamespaces()(CreateExchange))
);
)

@ -1,44 +1,44 @@
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { withNamespaces } from 'react-i18next';
import OversizedPanel from "../../components/OversizedPanel";
import Dropdown from "../../assets/images/dropdown-blue.svg";
import Modal from "../../components/Modal";
import {CSSTransitionGroup} from "react-transition-group";
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom'
import { withNamespaces } from 'react-i18next'
import OversizedPanel from '../../components/OversizedPanel'
import Dropdown from '../../assets/images/dropdown-blue.svg'
import Modal from '../../components/Modal'
import { CSSTransitionGroup } from 'react-transition-group'
const ADD = 'Add Liquidity';
const REMOVE = 'Remove Liquidity';
const CREATE = 'Create Exchange';
const ADD = 'Add Liquidity'
const REMOVE = 'Remove Liquidity'
const CREATE = 'Create Exchange'
class ModeSelector extends Component {
state = {
isShowingModal: false,
selected: ADD,
};
selected: ADD
}
changeView(view) {
const { history } = this.props;
const { history } = this.props
this.setState({
isShowingModal: false,
selected: view,
});
selected: view
})
switch (view) {
case ADD:
return history.push('/add-liquidity');
return history.push('/add-liquidity')
case REMOVE:
return history.push('/remove-liquidity');
return history.push('/remove-liquidity')
case CREATE:
return history.push('/create-exchange');
return history.push('/create-exchange')
default:
return;
return
}
}
renderModal() {
if (!this.state.isShowingModal) {
return;
return
}
return (
@ -52,41 +52,27 @@ class ModeSelector extends Component {
transitionEnterTimeout={200}
>
<div className="pool-modal">
<div
className="pool-modal__item"
onClick={() => this.changeView(ADD)}
>
{this.props.t("addLiquidity")}
<div className="pool-modal__item" onClick={() => this.changeView(ADD)}>
{this.props.t('addLiquidity')}
</div>
<div
className="pool-modal__item"
onClick={() => this.changeView(REMOVE)}
>
{this.props.t("removeLiquidity")}
<div className="pool-modal__item" onClick={() => this.changeView(REMOVE)}>
{this.props.t('removeLiquidity')}
</div>
<div
className="pool-modal__item"
onClick={() => this.changeView(CREATE)}
>
{this.props.t("createExchange")}
<div className="pool-modal__item" onClick={() => this.changeView(CREATE)}>
{this.props.t('createExchange')}
</div>
</div>
</CSSTransitionGroup>
</Modal>
);
)
}
render() {
return (
<OversizedPanel hideTop>
<div
className="pool__liquidity-container"
onClick={() => this.setState({ isShowingModal: true })}
>
<span className="pool__liquidity-label">
{this.props.title}
</span>
<img src={Dropdown} alt='dropdown' />
<div className="pool__liquidity-container" onClick={() => this.setState({ isShowingModal: true })}>
<span className="pool__liquidity-label">{this.props.title}</span>
<img src={Dropdown} alt="dropdown" />
</div>
{this.renderModal()}
</OversizedPanel>
@ -94,4 +80,4 @@ class ModeSelector extends Component {
}
}
export default withRouter(withNamespaces()(ModeSelector));
export default withRouter(withNamespaces()(ModeSelector))

@ -1,21 +1,21 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from "classnames";
import { connect } from 'react-redux';
import { BigNumber as BN } from 'bignumber.js';
import { withNamespaces } from 'react-i18next';
import NavigationTabs from "../../components/NavigationTabs";
import ModeSelector from "./ModeSelector";
import CurrencyInputPanel from "../../components/CurrencyInputPanel";
import { selectors, addPendingTx } from '../../ducks/web3connect';
import ContextualInfo from "../../components/ContextualInfo";
import OversizedPanel from "../../components/OversizedPanel";
import ArrowDownBlue from "../../assets/images/arrow-down-blue.svg";
import ArrowDownGrey from "../../assets/images/arrow-down-grey.svg";
import { getBlockDeadline } from '../../helpers/web3-utils';
import { retry } from '../../helpers/promise-utils';
import EXCHANGE_ABI from "../../abi/exchange";
import ReactGA from "react-ga";
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { connect } from 'react-redux'
import { BigNumber as BN } from 'bignumber.js'
import { withNamespaces } from 'react-i18next'
import NavigationTabs from '../../components/NavigationTabs'
import ModeSelector from './ModeSelector'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { selectors, addPendingTx } from '../../ducks/web3connect'
import ContextualInfo from '../../components/ContextualInfo'
import OversizedPanel from '../../components/OversizedPanel'
import ArrowDownBlue from '../../assets/images/arrow-down-blue.svg'
import ArrowDownGrey from '../../assets/images/arrow-down-grey.svg'
import { getBlockDeadline } from '../../helpers/web3-utils'
import { retry } from '../../helpers/promise-utils'
import EXCHANGE_ABI from '../../abi/exchange'
import ReactGA from 'react-ga'
class RemoveLiquidity extends Component {
static propTypes = {
@ -23,220 +23,242 @@ class RemoveLiquidity extends Component {
balances: PropTypes.object,
web3: PropTypes.object,
exchangeAddresses: PropTypes.shape({
fromToken: PropTypes.object.isRequired,
}).isRequired,
};
fromToken: PropTypes.object.isRequired
}).isRequired
}
state = {
tokenAddress: '',
value: '',
totalSupply: BN(0),
};
totalSupply: BN(0)
}
reset() {
this.setState({
value: '',
});
value: ''
})
}
validate() {
const { tokenAddress, value } = this.state;
const { t, account, selectors, exchangeAddresses: { fromToken }, web3 } = this.props;
const exchangeAddress = fromToken[tokenAddress];
const { tokenAddress, value } = this.state
const {
t,
account,
selectors,
exchangeAddresses: { fromToken },
web3
} = this.props
const exchangeAddress = fromToken[tokenAddress]
if (!web3 || !exchangeAddress || !account || !value) {
return {
isValid: false,
};
isValid: false
}
}
const { getBalance } = selectors();
const { getBalance } = selectors()
const { value: liquidityBalance, decimals: liquidityDecimals } = getBalance(account, exchangeAddress);
const { value: liquidityBalance, decimals: liquidityDecimals } = getBalance(account, exchangeAddress)
if (liquidityBalance.isLessThan(BN(value).multipliedBy(10 ** liquidityDecimals))) {
return { isValid: false, errorMessage: t("insufficientBalance") };
return { isValid: false, errorMessage: t('insufficientBalance') }
}
return {
isValid: true,
};
isValid: true
}
}
onTokenSelect = async tokenAddress => {
const { exchangeAddresses: { fromToken }, web3 } = this.props;
const exchangeAddress = fromToken[tokenAddress];
this.setState({ tokenAddress });
const {
exchangeAddresses: { fromToken },
web3
} = this.props
const exchangeAddress = fromToken[tokenAddress]
this.setState({ tokenAddress })
if (!web3 || !exchangeAddress) {
return;
return
}
const exchange = new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress);
const exchange = new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress)
const totalSupply = await exchange.methods.totalSupply().call();
const totalSupply = await exchange.methods.totalSupply().call()
this.setState({
totalSupply: BN(totalSupply),
});
};
totalSupply: BN(totalSupply)
})
}
onInputChange = value => {
this.setState({ value });
};
this.setState({ value })
}
onRemoveLiquidity = async () => {
const { tokenAddress, value: input, totalSupply } = this.state;
const { tokenAddress, value: input, totalSupply } = this.state
const {
exchangeAddresses: { fromToken },
web3,
selectors,
account,
} = this.props;
const exchangeAddress = fromToken[tokenAddress];
const { getBalance } = selectors();
account
} = this.props
const exchangeAddress = fromToken[tokenAddress]
const { getBalance } = selectors()
if (!web3 || !exchangeAddress) {
return;
return
}
const exchange = new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress);
const SLIPPAGE = .02;
const { decimals } = getBalance(account, exchangeAddress);
const { value: ethReserve } = getBalance(exchangeAddress);
const { value: tokenReserve } = getBalance(exchangeAddress, tokenAddress);
const amount = BN(input).multipliedBy(10 ** decimals);
const exchange = new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress)
const SLIPPAGE = 0.02
const { decimals } = getBalance(account, exchangeAddress)
const { value: ethReserve } = getBalance(exchangeAddress)
const { value: tokenReserve } = getBalance(exchangeAddress, tokenAddress)
const amount = BN(input).multipliedBy(10 ** decimals)
const ownership = amount.dividedBy(totalSupply);
const ethWithdrawn = ethReserve.multipliedBy(ownership);
const tokenWithdrawn = tokenReserve.multipliedBy(ownership);
let deadline;
const ownership = amount.dividedBy(totalSupply)
const ethWithdrawn = ethReserve.multipliedBy(ownership)
const tokenWithdrawn = tokenReserve.multipliedBy(ownership)
let deadline
try {
deadline = await retry(() => getBlockDeadline(web3, 600));
} catch(e) {
deadline = await retry(() => getBlockDeadline(web3, 600))
} catch (e) {
// TODO: Handle error.
return;
return
}
exchange.methods.removeLiquidity(
exchange.methods
.removeLiquidity(
amount.toFixed(0),
ethWithdrawn.multipliedBy(1 - SLIPPAGE).toFixed(0),
tokenWithdrawn.multipliedBy(1 - SLIPPAGE).toFixed(0),
deadline,
).send({ from: account }, (err, data) => {
deadline
)
.send({ from: account }, (err, data) => {
if (data) {
this.reset();
this.reset()
this.props.addPendingTx(data);
this.props.addPendingTx(data)
ReactGA.event({
category: 'Pool',
action: 'RemoveLiquidity',
});
action: 'RemoveLiquidity'
})
}
})
}
});
};
getBalance = () => {
const {
exchangeAddresses: { fromToken },
account,
web3,
selectors,
} = this.props;
selectors
} = this.props
const { tokenAddress } = this.state;
const { tokenAddress } = this.state
if (!web3) {
return '';
return ''
}
const exchangeAddress = fromToken[tokenAddress];
const exchangeAddress = fromToken[tokenAddress]
if (!exchangeAddress) {
return '';
return ''
}
const { value, decimals } = selectors().getBalance(account, exchangeAddress);
const { value, decimals } = selectors().getBalance(account, exchangeAddress)
if (!decimals) {
return '';
return ''
}
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(7)}`;
};
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(7)}`
}
renderSummary(errorMessage) {
const { t, selectors, exchangeAddresses: { fromToken } } = this.props;
const {
value: input,
tokenAddress,
} = this.state;
const inputIsZero = BN(input).isZero();
let contextualInfo = '';
let isError = false;
t,
selectors,
exchangeAddresses: { fromToken }
} = this.props
const { value: input, tokenAddress } = this.state
const inputIsZero = BN(input).isZero()
let contextualInfo = ''
let isError = false
if (errorMessage) {
contextualInfo = errorMessage;
isError = true;
contextualInfo = errorMessage
isError = true
} else if (!tokenAddress) {
contextualInfo = t("selectTokenCont");
contextualInfo = t('selectTokenCont')
} else if (inputIsZero) {
contextualInfo = t("noZero");
contextualInfo = t('noZero')
} else if (!input) {
const { label } = selectors().getTokenBalance(tokenAddress, fromToken[tokenAddress]);
contextualInfo = t("enterLabelCont", { label });
const { label } = selectors().getTokenBalance(tokenAddress, fromToken[tokenAddress])
contextualInfo = t('enterLabelCont', { label })
}
return (
<ContextualInfo
key="context-info"
openDetailsText={t("transactionDetails")}
closeDetailsText={t("hideDetails")}
openDetailsText={t('transactionDetails')}
closeDetailsText={t('hideDetails')}
contextualInfo={contextualInfo}
isError={isError}
renderTransactionDetails={this.renderTransactionDetails}
/>
);
)
}
renderTransactionDetails = () => {
const { tokenAddress, value: input, totalSupply } = this.state;
const { tokenAddress, value: input, totalSupply } = this.state
const {
t,
exchangeAddresses: { fromToken },
selectors,
account,
} = this.props;
const exchangeAddress = fromToken[tokenAddress];
const { getBalance } = selectors();
account
} = this.props
const exchangeAddress = fromToken[tokenAddress]
const { getBalance } = selectors()
if (!exchangeAddress) {
return null;
return null
}
ReactGA.event({
category: 'TransactionDetail',
action: 'Open',
});
action: 'Open'
})
const SLIPPAGE = 0.025;
const { decimals } = getBalance(account, exchangeAddress);
const { value: ethReserve } = getBalance(exchangeAddress);
const { value: tokenReserve, label } = getBalance(exchangeAddress, tokenAddress);
const SLIPPAGE = 0.025
const { decimals } = getBalance(account, exchangeAddress)
const { value: ethReserve } = getBalance(exchangeAddress)
const { value: tokenReserve, label } = getBalance(exchangeAddress, tokenAddress)
const ethPer = ethReserve.dividedBy(totalSupply);
const tokenPer = tokenReserve.dividedBy(totalSupply);
const ethPer = ethReserve.dividedBy(totalSupply)
const tokenPer = tokenReserve.dividedBy(totalSupply)
const ethWithdrawn = ethPer.multipliedBy(input);
const ethWithdrawn = ethPer.multipliedBy(input)
const tokenWithdrawn = tokenPer.multipliedBy(input);
const minTokenWithdrawn = tokenWithdrawn.multipliedBy(1 - SLIPPAGE).toFixed(7);
const maxTokenWithdrawn = tokenWithdrawn.multipliedBy(1 + SLIPPAGE).toFixed(7);
const tokenWithdrawn = tokenPer.multipliedBy(input)
const minTokenWithdrawn = tokenWithdrawn.multipliedBy(1 - SLIPPAGE).toFixed(7)
const maxTokenWithdrawn = tokenWithdrawn.multipliedBy(1 + SLIPPAGE).toFixed(7)
const adjTotalSupply = totalSupply.dividedBy(10 ** decimals).minus(input);
const adjTotalSupply = totalSupply.dividedBy(10 ** decimals).minus(input)
return (
<div>
<div className="pool__summary-modal__item">{t("youAreRemoving")} {b(`${+BN(ethWithdrawn).toFixed(7)} ETH`)} {t("and")} {b(`${+minTokenWithdrawn} - ${+maxTokenWithdrawn} ${label}`)} {t("outPool")}</div>
<div className="pool__summary-modal__item">{t("youWillRemove")} {b(+input)} {t("liquidityTokens")}</div>
<div className="pool__summary-modal__item">{t("totalSupplyIs")} {b(+adjTotalSupply.toFixed(7))}</div>
<div className="pool__summary-modal__item">{t("tokenWorth")} {b(+ethReserve.dividedBy(totalSupply).toFixed(7))} ETH {t("and")} {b(+tokenReserve.dividedBy(totalSupply).toFixed(7))} {label}</div>
<div className="pool__summary-modal__item">
{t('youAreRemoving')} {b(`${+BN(ethWithdrawn).toFixed(7)} ETH`)} {t('and')}{' '}
{b(`${+minTokenWithdrawn} - ${+maxTokenWithdrawn} ${label}`)} {t('outPool')}
</div>
);
<div className="pool__summary-modal__item">
{t('youWillRemove')} {b(+input)} {t('liquidityTokens')}
</div>
<div className="pool__summary-modal__item">
{t('totalSupplyIs')} {b(+adjTotalSupply.toFixed(7))}
</div>
<div className="pool__summary-modal__item">
{t('tokenWorth')} {b(+ethReserve.dividedBy(totalSupply).toFixed(7))} ETH {t('and')}{' '}
{b(+tokenReserve.dividedBy(totalSupply).toFixed(7))} {label}
</div>
</div>
)
}
renderOutput() {
@ -245,80 +267,77 @@ class RemoveLiquidity extends Component {
exchangeAddresses: { fromToken },
account,
web3,
selectors,
} = this.props;
const { getBalance } = selectors();
selectors
} = this.props
const { getBalance } = selectors()
const { tokenAddress, totalSupply, value: input } = this.state;
const { tokenAddress, totalSupply, value: input } = this.state
const blank = [
<CurrencyInputPanel
key="remove-liquidity-input"
title={t("output")}
description={`(${t("estimated")})`}
renderInput={() => (
<div className="remove-liquidity__output"></div>
)}
title={t('output')}
description={`(${t('estimated')})`}
renderInput={() => <div className="remove-liquidity__output" />}
disableTokenSelect
disableUnlock
/>,
<OversizedPanel key="remove-liquidity-input-under" hideBottom>
<div className="pool__summary-panel">
<div className="pool__exchange-rate-wrapper">
<span className="pool__exchange-rate">{t("exchangeRate")}</span>
<span className="pool__exchange-rate">{t('exchangeRate')}</span>
<span> - </span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">{t("currentPoolSize")}</span>
<span className="swap__exchange-rate">{t('currentPoolSize')}</span>
<span> - </span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">{t("yourPoolShare")}</span>
<span className="swap__exchange-rate">{t('yourPoolShare')}</span>
<span> - </span>
</div>
</div>
</OversizedPanel>
];
]
const exchangeAddress = fromToken[tokenAddress];
const exchangeAddress = fromToken[tokenAddress]
if (!exchangeAddress || !web3) {
return blank;
return blank
}
const { value: liquidityBalance } = getBalance(account, exchangeAddress);
const { value: ethReserve } = getBalance(exchangeAddress);
const { value: tokenReserve, decimals: tokenDecimals, label } = getBalance(exchangeAddress, tokenAddress);
const { value: liquidityBalance } = getBalance(account, exchangeAddress)
const { value: ethReserve } = getBalance(exchangeAddress)
const { value: tokenReserve, decimals: tokenDecimals, label } = getBalance(exchangeAddress, tokenAddress)
if (!tokenDecimals) {
return blank;
return blank
}
const ownership = liquidityBalance.dividedBy(totalSupply);
const ethPer = ethReserve.dividedBy(totalSupply);
const tokenPer = tokenReserve.multipliedBy(10 ** (18 - tokenDecimals)).dividedBy(totalSupply);
const exchangeRate = tokenReserve.multipliedBy(10 ** (18 - tokenDecimals)).div(ethReserve);
const ownership = liquidityBalance.dividedBy(totalSupply)
const ethPer = ethReserve.dividedBy(totalSupply)
const tokenPer = tokenReserve.multipliedBy(10 ** (18 - tokenDecimals)).dividedBy(totalSupply)
const exchangeRate = tokenReserve.multipliedBy(10 ** (18 - tokenDecimals)).div(ethReserve)
const ownedEth = ethPer.multipliedBy(liquidityBalance).dividedBy(10 ** 18);
const ownedToken = tokenPer.multipliedBy(liquidityBalance).dividedBy(10 ** tokenDecimals);
const ownedEth = ethPer.multipliedBy(liquidityBalance).dividedBy(10 ** 18)
const ownedToken = tokenPer.multipliedBy(liquidityBalance).dividedBy(10 ** tokenDecimals)
return [
<CurrencyInputPanel
title={t("output")}
description={`(${t("estimated")})`}
title={t('output')}
description={`(${t('estimated')})`}
key="remove-liquidity-input"
renderInput={() => input
? (
renderInput={() =>
input ? (
<div className="remove-liquidity__output">
<div className="remove-liquidity__output-text">
{`${ethPer.multipliedBy(input).toFixed(3)} ETH`}
</div>
<div className="remove-liquidity__output-text">{`${ethPer.multipliedBy(input).toFixed(3)} ETH`}</div>
<div className="remove-liquidity__output-plus"> + </div>
<div className="remove-liquidity__output-text">
{`${tokenPer.multipliedBy(input).toFixed(3)} ${label}`}
</div>
</div>
) : (
<div className="remove-liquidity__output" />
)
: <div className="remove-liquidity__output" />
}
disableTokenSelect
disableUnlock
@ -326,46 +345,46 @@ class RemoveLiquidity extends Component {
<OversizedPanel key="remove-liquidity-input-under" hideBottom>
<div className="pool__summary-panel">
<div className="pool__exchange-rate-wrapper">
<span className="pool__exchange-rate">{t("exchangeRate")}</span>
<span>
{`1 ETH = ${exchangeRate.toFixed(4)} ${label}`}
</span>
<span className="pool__exchange-rate">{t('exchangeRate')}</span>
<span>{`1 ETH = ${exchangeRate.toFixed(4)} ${label}`}</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">{t("currentPoolSize")}</span>
<span>{`${ethReserve.dividedBy(10 ** 18).toFixed(2)} ETH + ${tokenReserve.dividedBy(10 ** tokenDecimals).toFixed(2)} ${label}`}</span>
<span className="swap__exchange-rate">{t('currentPoolSize')}</span>
<span>{`${ethReserve.dividedBy(10 ** 18).toFixed(2)} ETH + ${tokenReserve
.dividedBy(10 ** tokenDecimals)
.toFixed(2)} ${label}`}</span>
</div>
<div className="pool__exchange-rate-wrapper">
<span className="swap__exchange-rate">
{t("yourPoolShare")} ({ownership.multipliedBy(100).toFixed(2)}%)
{t('yourPoolShare')} ({ownership.multipliedBy(100).toFixed(2)}%)
</span>
<span>{`${ownedEth.toFixed(2)} ETH + ${ownedToken.toFixed(2)} ${label}`}</span>
</div>
</div>
</OversizedPanel>
];
]
}
render() {
const { t, isConnected } = this.props;
const { tokenAddress, value } = this.state;
const { isValid, errorMessage } = this.validate();
const { t, isConnected } = this.props
const { tokenAddress, value } = this.state
const { isValid, errorMessage } = this.validate()
return [
<div
key="content"
className={classnames('swap__content', {
'swap--inactive': !isConnected,
'swap--inactive': !isConnected
})}
>
<NavigationTabs
className={classnames('header__navigation', {
'header--inactive': !isConnected,
'header--inactive': !isConnected
})}
/>
<ModeSelector title={t("removeLiquidity")} />
<ModeSelector title={t('removeLiquidity')} />
<CurrencyInputPanel
title={t("poolTokens")}
title={t('poolTokens')}
extraText={this.getBalance(tokenAddress)}
onValueChange={this.onInputChange}
value={value}
@ -376,41 +395,42 @@ class RemoveLiquidity extends Component {
/>
<OversizedPanel>
<div className="swap__down-arrow-background">
<img className="swap__down-arrow" src={isValid ? ArrowDownBlue : ArrowDownGrey} alt='arrow' />
<img className="swap__down-arrow" src={isValid ? ArrowDownBlue : ArrowDownGrey} alt="arrow" />
</div>
</OversizedPanel>
{ this.renderOutput() }
{ this.renderSummary(errorMessage) }
{this.renderOutput()}
{this.renderSummary(errorMessage)}
<div className="pool__cta-container">
<button
className={classnames('pool__cta-btn', {
'swap--inactive': !isConnected,
'pool__cta-btn--inactive': !isValid,
'pool__cta-btn--inactive': !isValid
})}
disabled={!isValid}
onClick={this.onRemoveLiquidity}
>
{t("removeLiquidity")}
{t('removeLiquidity')}
</button>
</div>
</div>
];
]
}
}
export default connect(
state => ({
isConnected: Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID||1),
isConnected:
Boolean(state.web3connect.account) && state.web3connect.networkId === (process.env.REACT_APP_NETWORK_ID || 1),
web3: state.web3connect.web3,
balances: state.web3connect.balances,
account: state.web3connect.account,
exchangeAddresses: state.addresses.exchangeAddresses,
exchangeAddresses: state.addresses.exchangeAddresses
}),
dispatch => ({
selectors: () => dispatch(selectors()),
addPendingTx: id => dispatch(addPendingTx(id)),
addPendingTx: id => dispatch(addPendingTx(id))
})
)(withNamespaces()(RemoveLiquidity));
)(withNamespaces()(RemoveLiquidity))
function b(text) {
return <span className="swap__highlight-text">{text}</span>

@ -1,17 +1,16 @@
import React, { Component } from 'react';
import Header from '../../components/Header';
import AddLiquidity from './AddLiquidity';
import CreateExchange from './CreateExchange';
import RemoveLiquidity from './RemoveLiquidity';
import { Switch, Route } from 'react-router-dom';
import "./pool.scss";
import MediaQuery from "react-responsive";
import ReactGA from "react-ga";
import React, { Component } from 'react'
import Header from '../../components/Header'
import AddLiquidity from './AddLiquidity'
import CreateExchange from './CreateExchange'
import RemoveLiquidity from './RemoveLiquidity'
import { Switch, Route } from 'react-router-dom'
import './pool.scss'
import MediaQuery from 'react-responsive'
import ReactGA from 'react-ga'
class Pool extends Component {
componentWillMount() {
ReactGA.pageview(window.location.pathname + window.location.search);
ReactGA.pageview(window.location.pathname + window.location.search)
}
render() {
return (
@ -25,8 +24,8 @@ class Pool extends Component {
<Route exact path="/create-exchange/:tokenAddress?" component={CreateExchange} />
</Switch>
</div>
);
)
}
}
export default Pool;
export default Pool

@ -1,4 +1,4 @@
@import "../../variables.scss";
@import '../../variables.scss';
.pool {
@extend %col-nowrap;
@ -8,16 +8,16 @@
&__liquidity-container {
@extend %row-nowrap;
align-items: center;
font-size: .75rem;
padding: .625rem 1rem;
font-size: .75rem;
font-size: 0.75rem;
padding: 0.625rem 1rem;
font-size: 0.75rem;
color: $royal-blue;
font-weight: 500;
cursor: pointer;
img {
height: .75rem;
width: .75rem;
height: 0.75rem;
width: 0.75rem;
}
}
@ -38,8 +38,8 @@
@extend %row-nowrap;
align-items: center;
color: $dove-gray;
font-size: .75rem;
padding: .25rem 1rem 0;
font-size: 0.75rem;
padding: 0.25rem 1rem 0;
}
&__exchange-rate {
@ -64,25 +64,25 @@
&__new-exchange-warning {
padding: 1rem;
margin-bottom: 2rem;
border: 1px solid rgba($pizazz-orange, .4);
background-color: rgba($pizazz-orange, .1);
border: 1px solid rgba($pizazz-orange, 0.4);
background-color: rgba($pizazz-orange, 0.1);
border-radius: 1rem;
}
&__new-exchange-warning-text {
font-size: .75rem;
font-size: 0.75rem;
line-height: 1rem;
text-align: center;
&:first-child {
padding-bottom: .3rem;
padding-bottom: 0.3rem;
font-weight: 500;
}
}
&__summary-item {
&:not(:last-child) {
margin-bottom: .5rem;
margin-bottom: 0.5rem;
}
}
}
@ -97,9 +97,9 @@
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
transition: 250ms ease-in-out;
padding: 1rem 0 .5rem;
padding: 1rem 0 0.5rem;
@media only screen and (min-width : 768px) {
@media only screen and (min-width: 768px) {
max-width: 560px;
position: absolute;
margin-left: auto;
@ -149,7 +149,7 @@
}
&__summary-text {
font-size: .75rem;
font-size: 0.75rem;
}
&--error {
@ -166,7 +166,7 @@
&__output-text {
font-size: 1.25rem;
line-height: 1.5rem;
padding: 1rem .75rem;
padding: 1rem 0.75rem;
}
&__output-plus {

File diff suppressed because it is too large Load Diff

@ -1,4 +1,4 @@
@import "../../variables.scss";
@import '../../variables.scss';
.send {
@extend %col-nowrap;

File diff suppressed because it is too large Load Diff

@ -1,4 +1,4 @@
@import "../../variables.scss";
@import '../../variables.scss';
.swap {
@extend %col-nowrap;
@ -6,22 +6,22 @@
background-color: $white;
&--inactive {
opacity: .5;
opacity: 0.5;
}
&__content {
padding: 1rem .75rem;
padding: 1rem 0.75rem;
flex: 1 1 auto;
height: 0;
overflow-y: auto;
}
&__down-arrow {
width: .625rem;
height: .625rem;
width: 0.625rem;
height: 0.625rem;
position: relative;
z-index: 200;
padding: .875rem;
padding: 0.875rem;
&--clickable {
cursor: pointer;
@ -38,8 +38,8 @@
@extend %row-nowrap;
align-items: center;
color: $dove-gray;
font-size: .75rem;
padding: .5rem 1rem;
font-size: 0.75rem;
padding: 0.5rem 1rem;
}
&__exchange-rate {

@ -13,28 +13,26 @@ const isLocalhost = Boolean(
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
)
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
const publicUrl = new URL(process.env.PUBLIC_URL, window.location)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
return
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
checkValidServiceWorker(swUrl)
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
@ -42,13 +40,13 @@ export default function register() {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://goo.gl/SC7cgQ'
);
});
)
})
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
registerValidSW(swUrl)
}
});
})
}
}
@ -57,7 +55,7 @@ function registerValidSW(swUrl) {
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
const installingWorker = registration.installing
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
@ -65,20 +63,20 @@ function registerValidSW(swUrl) {
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
console.log('New content is available; please refresh.')
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
console.log('Content is cached for offline use.')
}
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl) {
@ -86,32 +84,27 @@ function checkValidServiceWorker(swUrl) {
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
if (response.status === 404 || response.headers.get('content-type').indexOf('javascript') === -1) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
registerValidSW(swUrl)
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
console.log('No internet connection found. App is running in offline mode.')
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
registration.unregister()
})
}
}

@ -1,3 +1,3 @@
import store from './store.dev';
import store from './store.dev'
export default store;
export default store

@ -1 +1 @@
export default {};
export default {}

@ -1,19 +1,12 @@
import { applyMiddleware, compose, createStore } from 'redux';
import { applyMiddleware, compose, createStore } from 'redux'
import thunk from 'redux-thunk'
import initialState from './initial-state';
import reducer from '../ducks';
import initialState from './initial-state'
import reducer from '../ducks'
const middleware = [thunk];
const enhancers = [];
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const middleware = [thunk]
const enhancers = []
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(...middleware),
...enhancers,
)
);
const store = createStore(reducer, initialState, composeEnhancers(applyMiddleware(...middleware), ...enhancers))
export default store;
export default store

@ -2,26 +2,26 @@ $white: #fff;
$black: #000;
// Gray
$concrete-gray: #FAFAFA;
$mercury-gray: #E1E1E1;
$silver-gray: #C4C4C4;
$chalice-gray: #AEAEAE;
$concrete-gray: #fafafa;
$mercury-gray: #e1e1e1;
$silver-gray: #c4c4c4;
$chalice-gray: #aeaeae;
$dove-gray: #737373;
$mine-shaft-gray: #2B2B2B;
$mine-shaft-gray: #2b2b2b;
// Blue
$zumthor-blue: #EBF4FF;
$malibu-blue: #5CA2FF;
$royal-blue: #2F80ED;
$zumthor-blue: #ebf4ff;
$malibu-blue: #5ca2ff;
$royal-blue: #2f80ed;
// Purple
$wisteria-purple: #DC6BE5;
$wisteria-purple: #dc6be5;
// Red
$salmon-red: #FF6871;
$salmon-red: #ff6871;
// Orange
$pizazz-orange: #FF8F05;
$pizazz-orange: #ff8f05;
%col-nowrap {
display: flex;
@ -57,7 +57,6 @@ $pizazz-orange: #FF8F05;
&:disabled {
background-color: $mercury-gray;
}
}
%borderless-input {
@ -68,7 +67,6 @@ $pizazz-orange: #FF8F05;
flex: 1 1 auto;
width: 0;
&::placeholder {
color: $mercury-gray;
}

@ -9966,6 +9966,11 @@ preserve@^0.2.0:
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=
prettier@^1.17.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.17.0.tgz#53b303676eed22cc14a9f0cec09b477b3026c008"
integrity sha512-sXe5lSt2WQlCbydGETgfm1YBShgOX4HxQkFPvbxkcwgDvGDeqVau8h+12+lmSVlP3rHPz0oavfddSZg/q+Szjw==
pretty-bytes@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"