set up eslint and prettier; run prettier
This commit is contained in:
parent
da47f33b61
commit
71376cf7db
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
13
package.json
13
package.json
@ -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)
|
||||
})
|
||||
}
|
||||
|
14
src/i18n.js
14
src/i18n.js
@ -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
|
||||
|
29
src/index.js
29
src/index.js
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user