diff --git a/src/components/CurrencyInputPanel/index.js b/src/components/CurrencyInputPanel/index.js index 513461874f..ff9fc31208 100644 --- a/src/components/CurrencyInputPanel/index.js +++ b/src/components/CurrencyInputPanel/index.js @@ -56,6 +56,7 @@ class CurrencyInputPanel extends Component { addExchange: PropTypes.func.isRequired, filteredTokens: PropTypes.arrayOf(PropTypes.string), disableUnlock: PropTypes.bool, + renderInput: PropTypes.func, }; static defaultProps = { @@ -249,16 +250,76 @@ class CurrencyInputPanel extends Component { ); } + renderInput() { + const { + errorMessage, + value, + onValueChange, + selectedTokenAddress, + disableTokenSelect, + renderInput, + } = this.props; + + if (typeof renderInput === 'function') { + return renderInput(); + } + + return ( +
+ onValueChange(e.target.value)} + onKeyPress={e => { + const charCode = e.which ? e.which : e.keyCode; + + // Prevent 'minus' character + if (charCode === 45) { + e.preventDefault(); + e.stopPropagation(); + } + }} + value={value} + /> + { this.renderUnlockButton() } + +
+ ); + } + render() { const { title, description, extraText, errorMessage, - value, - onValueChange, - selectedTokenAddress, - disableTokenSelect, } = this.props; return ( @@ -277,52 +338,7 @@ class CurrencyInputPanel extends Component { {extraText} -
- onValueChange(e.target.value)} - onKeyPress={e => { - const charCode = e.which ? e.which : e.keyCode; - - // Prevent 'minus' character - if (charCode === 45) { - e.preventDefault(); - e.stopPropagation(); - } - }} - value={value} - /> - { this.renderUnlockButton() } - -
+ {this.renderInput()} {this.renderModal()} diff --git a/src/ducks/web3connect.js b/src/ducks/web3connect.js index 92e4025d54..0d01633a91 100644 --- a/src/ducks/web3connect.js +++ b/src/ducks/web3connect.js @@ -44,6 +44,7 @@ const initialState = { const TOKEN_LABEL_FALLBACK = { '0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359': 'DAI', '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2': 'MKR', + '0x9B913956036a3462330B0642B20D3879ce68b450': 'BAT + ETH' }; // selectors @@ -246,6 +247,7 @@ export const sync = () => async (dispatch, getState) => { } 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({ @@ -262,7 +264,17 @@ export const sync = () => async (dispatch, getState) => { const tokenBalance = getBalance(address, tokenAddress); const balance = await contract.methods.balanceOf(address).call(); const decimals = tokenBalance.decimals || await contract.methods.decimals().call(); - const symbol = TOKEN_LABEL_FALLBACK[tokenAddress] || tokenBalance.label || await contract.methods.symbol().call(); + let symbol = tokenBalance.symbol; + + try { + symbol = symbol || await contract.methods.symbol().call().catch(); + } catch (e) { + try { + symbol = symbol || web3.utils.hexToString(await contractBytes32.methods.symbol().call().catch()); + } catch (err) { + + } + } if (tokenBalance.value.isEqualTo(BN(balance)) && tokenBalance.label && tokenBalance.decimals) { return; diff --git a/src/pages/App.js b/src/pages/App.js index e2a3055dff..76525bf8e6 100644 --- a/src/pages/App.js +++ b/src/pages/App.js @@ -54,6 +54,7 @@ class App extends Component { + diff --git a/src/pages/Pool/RemoveLiquidity.js b/src/pages/Pool/RemoveLiquidity.js new file mode 100644 index 0000000000..b686b5a9f9 --- /dev/null +++ b/src/pages/Pool/RemoveLiquidity.js @@ -0,0 +1,294 @@ +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 NavigationTabs from "../../components/NavigationTabs"; +import ModeSelector from "./ModeSelector"; +import CurrencyInputPanel from "../../components/CurrencyInputPanel"; +import { selectors } from '../../ducks/web3connect'; +import OversizedPanel from "../../components/OversizedPanel"; +import ArrowPlus from "../../assets/images/plus-blue.svg"; +import EXCHANGE_ABI from "../../abi/exchange"; +import promisify from "../../helpers/web3-promisfy"; + +class RemoveLiquidity extends Component { + static propTypes = { + account: PropTypes.string, + balances: PropTypes.object, + web3: PropTypes.object, + exchangeAddresses: PropTypes.shape({ + fromToken: PropTypes.object.isRequired, + }).isRequired, + }; + + state = { + tokenAddress: '', + value: '', + totalSupply: BN(0), + }; + + reset() { + this.setState({ + value: '', + }); + } + + validate() { + const { tokenAddress, value } = this.state; + const { account, selectors, exchangeAddresses: { fromToken }, web3 } = this.props; + const exchangeAddress = fromToken[tokenAddress]; + + if (!web3 || !exchangeAddress || !account || !value) { + return { + isValid: false, + }; + } + + const { getBalance } = selectors(); + + const { value: liquidityBalance, decimals: liquidityDecimals } = getBalance(account, exchangeAddress); + + if (liquidityBalance.isLessThan(BN(value).multipliedBy(10 ** liquidityDecimals))) { + return { isValid: false, errorMessage: 'Insufficient balance' }; + } + + return { + isValid: true, + }; + } + + onTokenSelect = async tokenAddress => { + const { exchangeAddresses: { fromToken }, web3 } = this.props; + const exchangeAddress = fromToken[tokenAddress]; + this.setState({ tokenAddress }); + + if (!web3 || !exchangeAddress) { + return; + } + + const exchange = new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress); + + const totalSupply = await exchange.methods.totalSupply().call(); + this.setState({ + totalSupply: BN(totalSupply), + }); + }; + + onInputChange = value => { + this.setState({ value }); + }; + + onRemoveLiquidity = async () => { + const { tokenAddress, value: input, totalSupply } = this.state; + const { + exchangeAddresses: { fromToken }, + web3, + selectors, + account, + } = this.props; + const exchangeAddress = fromToken[tokenAddress]; + const { getBalance } = selectors(); + if (!web3 || !exchangeAddress) { + 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 ownership = amount.dividedBy(totalSupply); + const ethWithdrawn = ethReserve.multipliedBy(ownership); + const tokenWithdrawn = tokenReserve.multipliedBy(ownership); + const blockNumber = await promisify(web3, 'getBlockNumber'); + const block = await promisify(web3, 'getBlock', blockNumber); + const deadline = block.timestamp + 300; + + exchange.methods.removeLiquidity( + amount.toFixed(0), + ethWithdrawn.multipliedBy(1 - SLIPPAGE).toFixed(0), + tokenWithdrawn.multipliedBy(1 - SLIPPAGE).toFixed(0), + deadline, + ).send({ from: account }, (err, data) => { + if (data) { + this.reset(); + } + }); + }; + + getBalance = () => { + const { + exchangeAddresses: { fromToken }, + account, + web3, + selectors, + } = this.props; + + const { tokenAddress } = this.state; + + if (!web3) { + return ''; + } + + const exchangeAddress = fromToken[tokenAddress]; + if (!exchangeAddress) { + return ''; + } + const { value, decimals } = selectors().getBalance(account, exchangeAddress); + return `Balance: ${value.dividedBy(10 ** decimals).toFixed(7)}`; + }; + + renderOutput() { + const { + exchangeAddresses: { fromToken }, + account, + web3, + selectors, + } = this.props; + const { getBalance } = selectors(); + + const { tokenAddress, totalSupply, value: input } = this.state; + + + const exchangeAddress = fromToken[tokenAddress]; + if (!exchangeAddress || !web3 || !input) { + return [ + ( +
+ )} + disableTokenSelect + disableUnlock + />, + +
+
+ Exchange Rate + - +
+
+ Current Pool Size + - +
+
+ Your Pool Share + - +
+
+
+ ]; + } + const { value, decimals } = getBalance(account, exchangeAddress); + const { value: ethReserve } = getBalance(exchangeAddress); + const { value: tokenReserve, label } = getBalance(exchangeAddress, tokenAddress); + + const ownership = value.dividedBy(totalSupply); + const ethPer = ethReserve.dividedBy(totalSupply); + const tokenPer = tokenReserve.dividedBy(totalSupply); + + return [ + ( +
+
+ {`${ethPer.multipliedBy(input).toFixed(3)} ETH`} +
+
+
+
+ {`${tokenPer.multipliedBy(input).toFixed(3)} ${label}`} +
+
+ )} + disableTokenSelect + disableUnlock + />, + +
+
+ Exchange Rate + {` ${ethReserve.dividedBy(10 ** 18).toFixed(2)} ETH + ${tokenReserve.dividedBy(10 ** decimals).toFixed(2)} ${label}`} +
+
+ Current Pool Size + {totalSupply.dividedBy(10 ** decimals).toFixed(4)} +
+
+ Your Pool Share + {ownership.multipliedBy(100).toFixed(2)}% +
+
+
+ ]; + } + + render() { + const { isConnected } = this.props; + const { tokenAddress, value } = this.state; + const { isValid, errorMessage } = this.validate(); + + return ( +
+ + + + +
+ +
+
+ { this.renderOutput() } +
+ +
+
+ ); + } +} + +export default connect( + state => ({ + isConnected: Boolean(state.web3connect.account), + web3: state.web3connect.web3, + balances: state.web3connect.balances, + account: state.web3connect.account, + exchangeAddresses: state.addresses.exchangeAddresses, + }), + dispatch => ({ + selectors: () => dispatch(selectors()), + }) +)(RemoveLiquidity); diff --git a/src/pages/Pool/index.js b/src/pages/Pool/index.js index 0833e4c71d..7ec36badb7 100644 --- a/src/pages/Pool/index.js +++ b/src/pages/Pool/index.js @@ -2,7 +2,8 @@ import React, { Component } from 'react'; import Header from '../../components/Header'; import AddLiquidity from './AddLiquidity'; import CreateExchange from './CreateExchange'; -import { Switch, Redirect, Route } from 'react-router-dom'; +import RemoveLiquidity from './RemoveLiquidity'; +import { Switch, Route } from 'react-router-dom'; import "./pool.scss"; import MediaQuery from "react-responsive"; @@ -16,7 +17,7 @@ class Pool extends Component { - {/**/} + diff --git a/src/pages/Pool/pool.scss b/src/pages/Pool/pool.scss index faecf346ee..531a4f7736 100644 --- a/src/pages/Pool/pool.scss +++ b/src/pages/Pool/pool.scss @@ -150,3 +150,22 @@ color: $dove-gray; } } + +.remove-liquidity { + &__output { + @extend %row-nowrap; + min-height: 3.5rem; + } + + &__output-text { + font-size: 1.25rem; + line-height: 1.5rem; + padding: 1rem .75rem; + } + + &__output-plus { + font-size: 1.25rem; + line-height: 1.5rem; + padding: 1rem 0; + } +} \ No newline at end of file diff --git a/src/pages/Send/index.js b/src/pages/Send/index.js index e9275deb3d..8bbddd11dd 100644 --- a/src/pages/Send/index.js +++ b/src/pages/Send/index.js @@ -666,7 +666,7 @@ class Send extends Component { ); } - + console.log(outputLabel) return (
diff --git a/src/pages/Swap/index.js b/src/pages/Swap/index.js index bce1034b6e..ba55040dde 100644 --- a/src/pages/Swap/index.js +++ b/src/pages/Swap/index.js @@ -450,13 +450,6 @@ class Swap extends Component { return; } - console.log( - BN(outputValue).multipliedBy(10 ** outputDecimals).toFixed(0), - BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + TOKEN_ALLOWED_SLIPPAGE).toFixed(0), - inputAmountB.multipliedBy(1.2).toFixed(0), - deadline, - outputCurrency, - ) new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency]) .methods .tokenToTokenSwapOutput(