Commit 5987095e authored by Kenny Tran's avatar Kenny Tran Committed by GitHub

Merge pull request #84 from kennyt/refactor-send

 Refactor send to use web3connect
parents a28d0f0b 09e70021
import React, { Component } from 'react'; import React, { Component } from 'react';
import { drizzleConnect } from 'drizzle-react'; import { drizzleConnect } from 'drizzle-react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { isValidSend, updateField, addError, removeError, resetSend } from '../../ducks/send';
import { selectors, sync } from '../../ducks/web3connect';
import {BigNumber as BN} from "bignumber.js"; import {BigNumber as BN} from "bignumber.js";
import deepEqual from 'deep-equal'; import { selectors } from '../../ducks/web3connect';
import Header from '../../components/Header'; import Header from '../../components/Header';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import AddressInputPanel from '../../components/AddressInputPanel'; import AddressInputPanel from '../../components/AddressInputPanel';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel'; import OversizedPanel from '../../components/OversizedPanel';
import ArrowDown from '../../assets/images/arrow-down-blue.svg'; import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import Pending from '../../assets/images/pending.svg'; import EXCHANGE_ABI from '../../abi/exchange';
import {
calculateExchangeRateFromInput,
calculateExchangeRateFromOutput,
sendInput,
sendOutput,
} from '../../helpers/exchange-utils';
import {
isExchangeUnapproved,
approveExchange,
} from '../../helpers/approval-utils';
import {
getTxStatus
} from '../../helpers/contract-utils';
import "./send.scss"; import "./send.scss";
import promisify from "../../helpers/web3-promisfy";
const INPUT = 0;
const OUTPUT = 1;
class Send extends Component { class Send extends Component {
static propTypes = { static propTypes = {
// Injected by React Router Dom account: PropTypes.string,
push: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
currentAddress: PropTypes.string,
isConnected: PropTypes.bool.isRequired, isConnected: PropTypes.bool.isRequired,
updateField: PropTypes.func.isRequired, selectors: PropTypes.func.isRequired,
input: PropTypes.string, web3: PropTypes.object.isRequired,
output: PropTypes.string,
inputCurrency: PropTypes.string,
outputCurrency: PropTypes.string,
recipient: PropTypes.string,
lastEditedField: PropTypes.string,
};
static contextTypes = {
drizzle: PropTypes.object,
}; };
state = { state = {
exchangeRate: BN(0), inputValue: '',
approvalTxId: null, outputValue: '',
sendTxId: null, inputCurrency: '',
outputCurrency: '',
inputAmountB: '',
lastEditedField: '',
recipient: '',
}; };
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return !deepEqual(nextProps, this.props) || return true;
!deepEqual(nextState, this.state);
}
componentDidUpdate() {
if (this.getSendStatus() === 'pending') {
this.resetSend();
}
this.getExchangeRate(this.props)
.then(exchangeRate => {
if (this.state.exchangeRate !== exchangeRate) {
this.setState({ exchangeRate });
}
if (!exchangeRate) {
return;
}
if (this.props.lastEditedField === 'input') {
this.props.updateField('output', `${BN(this.props.input).multipliedBy(exchangeRate).toFixed(7)}`);
} else if (this.props.lastEditedField === 'output') {
this.props.updateField('input', `${BN(this.props.output).multipliedBy(BN(1).dividedBy(exchangeRate)).toFixed(7)}`);
}
});
} }
componentWillUnmount() { reset() {
this.resetSend(); this.setState({
inputValue: '',
outputValue: '',
inputCurrency: '',
outputCurrency: '',
inputAmountB: '',
lastEditedField: '',
recipient: '',
});
} }
resetSend() { componentWillReceiveProps() {
this.props.resetSend(); this.recalcForm();
this.setState({approvalTxId: null, sendTxId: null});
} }
getSendStatus() { validate() {
const { drizzle } = this.context; const { selectors, account } = this.props;
const {
inputValue, outputValue,
inputCurrency, outputCurrency,
recipient,
} = this.state;
return getTxStatus({ let inputError = '';
drizzleCtx: drizzle, let outputError = '';
txId: this.state.sendTxId, let isValid = true;
});
}
getTokenLabel(address) { if (!inputValue || !outputValue || !inputCurrency || !outputCurrency || !recipient) {
if (address === 'ETH') { isValid = false;
return 'ETH';
} }
const { const { value: inputBalance, decimals: inputDecimals } = selectors().getBalance(account, inputCurrency);
initialized,
contracts,
} = this.props;
const { drizzle } = this.context;
const { web3 } = drizzle;
if (!initialized || !web3 || !address) { if (inputBalance.isLessThan(BN(inputValue * 10 ** inputDecimals))) {
return ''; inputError = 'Insufficient Balance';
} }
const symbolKey = drizzle.contracts[address].methods.symbol.cacheCall(); if (inputValue === 'N/A') {
const token = contracts[address]; inputError = 'Not a valid input value';
const symbol = token.symbol[symbolKey];
if (!symbol) {
return '';
} }
return symbol.value; return {
inputError,
outputError,
isValid: isValid && !inputError && !outputError,
};
} }
getBalance(currency) { recalcForm() {
const { selectors, account } = this.props; const { inputCurrency, outputCurrency } = this.state;
if (!currency) { if (!inputCurrency || !outputCurrency) {
return ''; return;
} }
if (currency === 'ETH') { if (inputCurrency === outputCurrency) {
const { value, decimals } = selectors().getBalance(account); this.setState({
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`; inputValue: '',
outputValue: '',
});
return;
} }
const { value, decimals } = selectors().getTokenBalance(currency, account); if (inputCurrency !== 'ETH' && outputCurrency !== 'ETH') {
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`; this.recalcTokenTokenForm();
} return;
updateInput(amount) {
this.props.updateField('input', amount);
if (!amount) {
this.props.updateField('output', '');
} }
this.props.updateField('lastEditedField', 'input');
}
updateOutput(amount) { this.recalcEthTokenForm();
this.props.updateField('output', amount);
if (!amount) {
this.props.updateField('input', '');
}
this.props.updateField('lastEditedField', 'output');
} }
async getExchangeRate(props) { recalcTokenTokenForm = () => {
const { const {
input, exchangeAddresses: { fromToken },
output, selectors,
} = this.props;
const {
inputValue: oldInputValue,
outputValue: oldOutputValue,
inputCurrency, inputCurrency,
outputCurrency, outputCurrency,
exchangeAddresses,
lastEditedField, lastEditedField,
contracts, exchangeRate: oldExchangeRate,
} = props; inputAmountB: oldInputAmountB,
} = this.state;
const { drizzle } = this.context;
const exchangeAddressA = fromToken[inputCurrency];
return lastEditedField === 'input' const exchangeAddressB = fromToken[outputCurrency];
? await calculateExchangeRateFromInput({
drizzleCtx: drizzle, const { value: inputReserveA, decimals: inputDecimalsA } = selectors().getBalance(exchangeAddressA, inputCurrency);
contractStore: contracts, const { value: outputReserveA }= selectors().getBalance(exchangeAddressA, 'ETH');
input, const { value: inputReserveB } = selectors().getBalance(exchangeAddressB, 'ETH');
output, const { value: outputReserveB, decimals: outputDecimalsB }= selectors().getBalance(exchangeAddressB, outputCurrency);
inputCurrency,
outputCurrency, if (lastEditedField === INPUT) {
exchangeAddresses, if (!oldInputValue) {
}) return this.setState({
: await calculateExchangeRateFromOutput({ outputValue: '',
drizzleCtx: drizzle, exchangeRate: BN(0),
contractStore: contracts, });
input, }
output,
inputCurrency, const inputAmountA = BN(oldInputValue).multipliedBy(10 ** inputDecimalsA);
outputCurrency, const outputAmountA = calculateEtherTokenOutput({
exchangeAddresses, inputAmount: inputAmountA,
}) ; inputReserve: inputReserveA,
} outputReserve: outputReserveA,
});
// Redundant Variable for readability of the formala
// OutputAmount from the first send becomes InputAmount of the second send
const inputAmountB = outputAmountA;
const outputAmountB = calculateEtherTokenOutput({
inputAmount: inputAmountB,
inputReserve: inputReserveB,
outputReserve: outputReserveB,
});
getIsUnapproved() { const exchangeRate = outputAmountB.dividedBy(inputAmountA);
const { const outputValue = outputAmountB.dividedBy(BN(10 ** outputDecimalsB)).toFixed(7);
input,
inputCurrency,
account,
contracts,
exchangeAddresses
} = this.props;
const { drizzle } = this.context;
return isExchangeUnapproved({ const appendState = {};
value: input,
currency: inputCurrency,
drizzleCtx: drizzle,
contractStore: contracts,
account,
exchangeAddresses,
});
}
approveExchange = async () => { if (!exchangeRate.isEqualTo(BN(oldExchangeRate))) {
const { appendState.exchangeRate = exchangeRate;
inputCurrency, }
exchangeAddresses,
account, if (outputValue !== oldOutputValue) {
contracts, appendState.outputValue = outputValue;
} = this.props; }
const { drizzle } = this.context;
this.setState(appendState);
if (this.getIsUnapproved()) { }
const approvalTxId = await approveExchange({
currency: inputCurrency, if (lastEditedField === OUTPUT) {
drizzleCtx: drizzle, if (!oldOutputValue) {
contractStore: contracts, return this.setState({
account, inputValue: '',
exchangeAddresses, exchangeRate: BN(0),
});
}
const outputAmountB = BN(oldOutputValue).multipliedBy(10 ** outputDecimalsB);
const inputAmountB = calculateEtherTokenInput({
outputAmount: outputAmountB,
inputReserve: inputReserveB,
outputReserve: outputReserveB,
});
// Redundant Variable for readability of the formala
// InputAmount from the first send becomes OutputAmount of the second send
const outputAmountA = inputAmountB;
const inputAmountA = calculateEtherTokenInput({
outputAmount: outputAmountA,
inputReserve: inputReserveA,
outputReserve: outputReserveA,
}); });
this.setState({ approvalTxId }) const exchangeRate = outputAmountB.dividedBy(inputAmountA);
const inputValue = inputAmountA.isNegative()
? 'N/A'
: inputAmountA.dividedBy(BN(10 ** inputDecimalsA)).toFixed(7);
const appendState = {};
if (!exchangeRate.isEqualTo(BN(oldExchangeRate))) {
appendState.exchangeRate = exchangeRate;
}
if (inputValue !== oldInputValue) {
appendState.inputValue = inputValue;
}
if (!inputAmountB.isEqualTo(BN(oldInputAmountB))) {
appendState.inputAmountB = inputAmountB;
}
this.setState(appendState);
} }
}
getApprovalStatus() { };
const { drizzle } = this.context;
return getTxStatus({ recalcEthTokenForm = () => {
drizzleCtx: drizzle, const {
txId: this.state.approvalTxId, exchangeAddresses: { fromToken },
}); selectors,
} } = this.props;
onSend = async () => {
const { const {
input, inputValue: oldInputValue,
output, outputValue: oldOutputValue,
inputCurrency, inputCurrency,
outputCurrency, outputCurrency,
recipient,
exchangeAddresses,
lastEditedField, lastEditedField,
account, exchangeRate: oldExchangeRate,
contracts, } = this.state;
selectors,
} = this.props;
const { drizzle } = this.context; const tokenAddress = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0];
const { decimals: inputDecimals } = inputCurrency === 'ETH' ? const exchangeAddress = fromToken[tokenAddress];
selectors().getBalance(account) if (!exchangeAddress) {
: selectors().getTokenBalance(inputCurrency, account); return;
const { decimals: outputDecimals } = outputCurrency === 'ETH' ?
selectors().getBalance(account)
: selectors().getTokenBalance(outputCurrency, account);
let sendTxId;
if (lastEditedField === 'input') {
sendTxId = await sendInput({
drizzleCtx: drizzle,
contractStore: contracts,
input,
output,
inputCurrency,
outputCurrency,
recipient,
exchangeAddresses,
account,
inputDecimals,
outputDecimals,
});
} }
const { value: inputReserve, decimals: inputDecimals } = selectors().getBalance(exchangeAddress, inputCurrency);
if (lastEditedField === 'output') { const { value: outputReserve, decimals: outputDecimals }= selectors().getBalance(exchangeAddress, outputCurrency);
sendTxId = await sendOutput({
drizzleCtx: drizzle, if (lastEditedField === INPUT) {
contractStore: contracts, if (!oldInputValue) {
input, return this.setState({
output, outputValue: '',
inputCurrency, exchangeRate: BN(0),
outputCurrency, });
recipient, }
exchangeAddresses,
account, const inputAmount = BN(oldInputValue).multipliedBy(10 ** inputDecimals);
inputDecimals, const outputAmount = calculateEtherTokenOutput({ inputAmount, inputReserve, outputReserve });
outputDecimals, const exchangeRate = outputAmount.dividedBy(inputAmount);
}); const outputValue = outputAmount.dividedBy(BN(10 ** outputDecimals)).toFixed(7);
const appendState = {};
if (!exchangeRate.isEqualTo(BN(oldExchangeRate))) {
appendState.exchangeRate = exchangeRate;
}
if (outputValue !== oldOutputValue) {
appendState.outputValue = outputValue;
}
this.setState(appendState);
} else if (lastEditedField === OUTPUT) {
if (!oldOutputValue) {
return this.setState({
inputValue: '',
exchangeRate: BN(0),
});
}
const outputAmount = BN(oldOutputValue).multipliedBy(10 ** outputDecimals);
const inputAmount = calculateEtherTokenInput({ outputAmount, inputReserve, outputReserve });
const exchangeRate = outputAmount.dividedBy(inputAmount);
const inputValue = inputAmount.isNegative()
? 'N/A'
: inputAmount.dividedBy(BN(10 ** inputDecimals)).toFixed(7);
const appendState = {};
if (!exchangeRate.isEqualTo(BN(oldExchangeRate))) {
appendState.exchangeRate = exchangeRate;
}
if (inputValue !== oldInputValue) {
appendState.inputValue = inputValue;
}
this.setState(appendState);
} }
};
this.setState({ sendTxId }); updateInput = amount => {
this.setState({
inputValue: amount,
lastEditedField: INPUT,
}, this.recalcForm);
}; };
handleSubButtonClick = () => { updateOutput = amount => {
if (this.getIsUnapproved() && this.getApprovalStatus() !== 'pending') { this.setState({
this.approveExchange(); outputValue: amount,
} lastEditedField: OUTPUT,
} }, this.recalcForm);
};
validate() { onSend = async () => {
const { const {
selectors, exchangeAddresses: { fromToken },
account, account,
input, web3,
output, selectors,
inputCurrency,
outputCurrency
} = this.props; } = this.props;
const {
let inputError; inputValue,
let isValid = true; outputValue,
inputCurrency,
if (!input || !output || !inputCurrency || !outputCurrency) { outputCurrency,
isValid = false; inputAmountB,
lastEditedField,
recipient,
} = this.state;
const ALLOWED_SLIPPAGE = 0.025;
const TOKEN_ALLOWED_SLIPPAGE = 0.04;
const type = getSendType(inputCurrency, outputCurrency);
const { decimals: inputDecimals } = selectors().getBalance(account, inputCurrency);
const { decimals: outputDecimals } = selectors().getBalance(account, outputCurrency);
const blockNumber = await promisify(web3, 'getBlockNumber');
const block = await promisify(web3, 'getBlock', blockNumber);
const deadline = block.timestamp + 300;
if (lastEditedField === INPUT) {
// send input
switch(type) {
case 'ETH_TO_TOKEN':
new web3.eth.Contract(EXCHANGE_ABI, fromToken[outputCurrency])
.methods
.ethToTokenTransferInput(
BN(outputValue).multipliedBy(10 ** outputDecimals).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(0),
deadline,
recipient,
)
.send({
from: account,
value: BN(inputValue).multipliedBy(10 ** 18).toFixed(0),
}, err => !err && this.reset());
break;
case 'TOKEN_TO_ETH':
new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency])
.methods
.tokenToEthTransferInput(
BN(inputValue).multipliedBy(10 ** inputDecimals).toFixed(0),
BN(outputValue).multipliedBy(10 ** outputDecimals).multipliedBy(1 - ALLOWED_SLIPPAGE).toFixed(0),
deadline,
recipient,
)
.send({
from: account,
}, err => !err && this.reset());
break;
case 'TOKEN_TO_TOKEN':
new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency])
.methods
.tokenToTokenTransferInput(
BN(inputValue).multipliedBy(10 ** inputDecimals).toFixed(0),
BN(outputValue).multipliedBy(10 ** outputDecimals).multipliedBy(1 - TOKEN_ALLOWED_SLIPPAGE).toFixed(0),
'1',
deadline,
recipient,
outputCurrency,
)
.send({
from: account,
}, err => !err && this.reset());
break;
default:
break;
}
} }
const { value: inputBalance, decimals: inputDecimals } = inputCurrency === 'ETH' ? if (lastEditedField === OUTPUT) {
selectors().getBalance(account) // send output
: selectors().getTokenBalance(inputCurrency, account); switch (type) {
case 'ETH_TO_TOKEN':
if (inputBalance.isLessThan(BN(input * 10 ** inputDecimals))) { new web3.eth.Contract(EXCHANGE_ABI, fromToken[outputCurrency])
inputError = 'Insufficient Balance'; .methods
.ethToTokenTransferOutput(
BN(outputValue).multipliedBy(10 ** outputDecimals).toFixed(0),
deadline,
recipient,
)
.send({
from: account,
value: BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0),
}, err => !err && this.reset());
break;
case 'TOKEN_TO_ETH':
new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency])
.methods
.tokenToEthTransferOutput(
BN(outputValue).multipliedBy(10 ** outputDecimals).toFixed(0),
BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0),
deadline,
recipient,
)
.send({
from: account,
}, err => !err && this.reset());
break;
case 'TOKEN_TO_TOKEN':
if (!inputAmountB) {
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
.tokenToTokenTransferOutput(
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,
recipient,
outputCurrency,
).send({
from: account,
}, err => !err && this.reset());
break;
default:
break;
}
} }
};
return { renderSummary() {
inputError,
outputError: '',
isValid: isValid && !inputError,
};
}
renderSummary(inputError, outputError, inputLabel, outputLabel) {
const { const {
selectors, inputValue,
input,
output,
inputCurrency, inputCurrency,
inputError,
outputValue,
outputCurrency, outputCurrency,
outputError,
recipient, recipient,
} = this.props; } = this.state;
const { exchangeRate } = this.state;
const { selectors, account } = this.props;
const { label: inputLabel } = selectors().getBalance(account, inputCurrency);
const { label: outputLabel } = selectors().getBalance(account, outputCurrency);
let nextStepMessage; let nextStepMessage;
if (inputError || outputError) { if (inputError || outputError) {
...@@ -362,8 +469,8 @@ class Send extends Component { ...@@ -362,8 +469,8 @@ class Send extends Component {
nextStepMessage = 'Select a token to continue.'; nextStepMessage = 'Select a token to continue.';
} else if (inputCurrency === outputCurrency) { } else if (inputCurrency === outputCurrency) {
nextStepMessage = 'Must be different token.'; nextStepMessage = 'Must be different token.';
} else if (!input || !output) { } else if (!inputValue || !outputValue) {
const missingCurrencyValue = !input ? inputCurrency : outputCurrency; const missingCurrencyValue = !inputValue ? inputLabel : outputLabel;
nextStepMessage = `Enter a ${missingCurrencyValue} value to continue.`; nextStepMessage = `Enter a ${missingCurrencyValue} value to continue.`;
} else if (!recipient) { } else if (!recipient) {
nextStepMessage = 'Enter a wallet address to send to.'; nextStepMessage = 'Enter a wallet address to send to.';
...@@ -378,34 +485,69 @@ class Send extends Component { ...@@ -378,34 +485,69 @@ class Send extends Component {
} }
const SLIPPAGE = 0.025; const SLIPPAGE = 0.025;
const exchangedOutput = BN(input).multipliedBy(BN(exchangeRate)); const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE).toFixed(2);
const minOutput = exchangedOutput.multipliedBy(1 - SLIPPAGE).toFixed(8); const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE).toFixed(2);
const maxOutput = exchangedOutput.multipliedBy(1 + SLIPPAGE).toFixed(8);
return ( return (
<div className="swap__summary-wrapper"> <div className="swap__summary-wrapper">
<div> <div>
You are selling {b(`${input} ${inputLabel}`)} You are selling {b(`${inputValue} ${inputLabel}`)}
</div> </div>
<div className="send__last-summary-text"> <div className="send__last-summary-text">
<span className="swap__highlight-text">{recipient.slice(0, 6)}</span> <span className="swap__highlight-text">{recipient.slice(0, 6)}</span> will receive between {b(`${minOutput} ${outputLabel}`)} and {b(`${maxOutput} ${outputLabel}`)}
will receive between {b(`${minOutput} ${outputLabel}`)} and {b(`${maxOutput} ${outputLabel}`)}
</div> </div>
</div> </div>
) )
} }
renderExchangeRate() {
const { account, selectors } = this.props;
const { exchangeRate, inputCurrency, outputCurrency } = this.state;
const { label: inputLabel } = selectors().getBalance(account, inputCurrency);
const { label: outputLabel } = selectors().getBalance(account, outputCurrency);
if (!exchangeRate || exchangeRate.isNaN() || !inputCurrency || !outputCurrency) {
return (
<OversizedPanel hideBottom>
<div className="swap__exchange-rate-wrapper">
<span className="swap__exchange-rate">Exchange Rate</span>
<span> - </span>
</div>
</OversizedPanel>
);
}
return (
<OversizedPanel hideBottom>
<div className="swap__exchange-rate-wrapper">
<span className="swap__exchange-rate">Exchange Rate</span>
<span>
{`1 ${inputLabel} = ${exchangeRate.toFixed(7)} ${outputLabel}`}
</span>
</div>
</OversizedPanel>
);
}
render() { render() {
const { lastEditedField, inputCurrency, outputCurrency, input, output, recipient } = this.props; const { selectors, account } = this.props;
const { exchangeRate } = this.state; const {
const inputLabel = this.getTokenLabel(inputCurrency); lastEditedField,
const outputLabel = this.getTokenLabel(outputCurrency); inputCurrency,
outputCurrency,
inputValue,
outputValue,
recipient,
} = this.state;
const estimatedText = '(estimated)'; const estimatedText = '(estimated)';
const { value: inputBalance, decimals: inputDecimals } = selectors().getBalance(account, inputCurrency);
const { value: outputBalance, decimals: outputDecimals } = selectors().getBalance(account, outputCurrency);
const { inputError, outputError, isValid } = this.validate(); const { inputError, outputError, isValid } = this.validate();
return ( return (
<div className="send"> <div className="swap">
<Header /> <Header />
<div <div
className={classnames('swap__content', { className={classnames('swap__content', {
...@@ -414,17 +556,17 @@ class Send extends Component { ...@@ -414,17 +556,17 @@ class Send extends Component {
> >
<CurrencyInputPanel <CurrencyInputPanel
title="Input" title="Input"
description={lastEditedField === 'output' ? estimatedText : ''} description={lastEditedField === OUTPUT ? estimatedText : ''}
onCurrencySelected={(d) => { extraText={inputCurrency
this.props.updateField('inputCurrency', d) ? `Balance: ${inputBalance.dividedBy(BN(10 ** inputDecimals)).toFixed(4)}`
this.props.sync(); : ''
}} }
onValueChange={d => this.updateInput(d)} onCurrencySelected={inputCurrency => this.setState({ inputCurrency }, this.recalcForm)}
onValueChange={this.updateInput}
selectedTokens={[inputCurrency, outputCurrency]} selectedTokens={[inputCurrency, outputCurrency]}
errorMessage={inputError}
value={input}
selectedTokenAddress={inputCurrency} selectedTokenAddress={inputCurrency}
extraText={this.getBalance(inputCurrency)} value={inputValue}
errorMessage={inputError}
/> />
<OversizedPanel> <OversizedPanel>
<div className="swap__down-arrow-background"> <div className="swap__down-arrow-background">
...@@ -433,17 +575,17 @@ class Send extends Component { ...@@ -433,17 +575,17 @@ class Send extends Component {
</OversizedPanel> </OversizedPanel>
<CurrencyInputPanel <CurrencyInputPanel
title="Output" title="Output"
description={lastEditedField === 'input' ? estimatedText : ''} description={lastEditedField === INPUT ? estimatedText : ''}
onCurrencySelected={(d) => { extraText={outputCurrency
this.props.updateField('outputCurrency', d) ? `Balance: ${outputBalance.dividedBy(BN(10 ** outputDecimals)).toFixed(4)}`
this.props.sync(); : ''
}} }
onValueChange={d => this.updateOutput(d)} onCurrencySelected={outputCurrency => this.setState({ outputCurrency }, this.recalcForm)}
onValueChange={this.updateOutput}
selectedTokens={[inputCurrency, outputCurrency]} selectedTokens={[inputCurrency, outputCurrency]}
errorMessage={outputError} value={outputValue}
value={output}
selectedTokenAddress={outputCurrency} selectedTokenAddress={outputCurrency}
extraText={this.getBalance(outputCurrency)} errorMessage={outputError}
disableUnlock disableUnlock
/> />
<OversizedPanel> <OversizedPanel>
...@@ -453,71 +595,90 @@ class Send extends Component { ...@@ -453,71 +595,90 @@ class Send extends Component {
</OversizedPanel> </OversizedPanel>
<AddressInputPanel <AddressInputPanel
value={recipient} value={recipient}
onChange={address => this.props.updateField('recipient', address)} onChange={address => this.setState({recipient: address})}
/> />
<OversizedPanel hideBottom> { this.renderExchangeRate() }
<div className="swap__exchange-rate-wrapper"> { this.renderSummary() }
<span className="swap__exchange-rate">Exchange Rate</span>
<span>
{exchangeRate ? `1 ${inputLabel} = ${exchangeRate.toFixed(7)} ${outputLabel}` : ' - '}
</span>
</div>
</OversizedPanel>
{ this.renderSummary(inputError, outputError, inputLabel, outputLabel) }
</div> </div>
<button <button
className={classnames('swap__cta-btn', { className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected, 'swap--inactive': !this.props.isConnected,
'swap__cta-btn--inactive': !isValid,
})} })}
disabled={!isValid} disabled={!isValid}
onClick={this.onSend} onClick={this.onSend}
> >
Send Send
</button> </button>
</div> </div>
); );
} }
} }
export default withRouter( export default drizzleConnect(
drizzleConnect( Send,
Send, state => ({
(state, ownProps) => ({ balances: state.web3connect.balances,
// React Router isConnected: !!state.web3connect.account,
push: ownProps.history.push, account: state.web3connect.account,
pathname: ownProps.location.pathname, web3: state.web3connect.web3,
exchangeAddresses: state.addresses.exchangeAddresses,
// From Drizzle }),
initialized: state.drizzleStatus.initialized, dispatch => ({
balance: state.accountBalances[state.accounts[0]] || null, selectors: () => dispatch(selectors()),
account: state.accounts[0], }),
contracts: state.contracts,
currentAddress: state.accounts[0],
isConnected: !!(state.drizzleStatus.initialized && state.accounts[0]),
// Redux Store
balances: state.web3connect.balances,
input: state.send.input,
output: state.send.output,
inputCurrency: state.send.inputCurrency,
outputCurrency: state.send.outputCurrency,
recipient: state.send.recipient,
lastEditedField: state.send.lastEditedField,
exchangeAddresses: state.addresses.exchangeAddresses,
}),
dispatch => ({
updateField: (name, value) => dispatch(updateField({ name, value })),
addError: (name, value) => dispatch(addError({ name, value })),
removeError: (name, value) => dispatch(removeError({ name, value })),
resetSend: () => dispatch(resetSend()),
selectors: () => dispatch(selectors()),
sync: () => dispatch(sync()),
})
),
); );
function b(text) { const b = text => <span className="swap__highlight-text">{text}</span>;
return <span className="swap__highlight-text">{text}</span>
function calculateEtherTokenOutput({ inputAmount: rawInput, inputReserve: rawReserveIn, outputReserve: rawReserveOut }) {
const inputAmount = BN(rawInput);
const inputReserve = BN(rawReserveIn);
const outputReserve = BN(rawReserveOut);
if (inputAmount.isLessThan(BN(10 ** 9))) {
console.warn(`inputAmount is only ${inputAmount.toFixed(0)}. Did you forget to multiply by 10 ** decimals?`);
}
const numerator = inputAmount.multipliedBy(outputReserve).multipliedBy(997);
const denominator = inputReserve.multipliedBy(1000).plus(inputAmount.multipliedBy(997));
return numerator.dividedBy(denominator);
}
function calculateEtherTokenInput({ outputAmount: rawOutput, inputReserve: rawReserveIn, outputReserve: rawReserveOut }) {
const outputAmount = BN(rawOutput);
const inputReserve = BN(rawReserveIn);
const outputReserve = BN(rawReserveOut);
if (outputAmount.isLessThan(BN(10 ** 9))) {
console.warn(`inputAmount is only ${outputAmount.toFixed(0)}. Did you forget to multiply by 10 ** decimals?`);
}
const numerator = outputAmount.multipliedBy(inputReserve).multipliedBy(1000);
const denominator = outputReserve.minus(outputAmount).multipliedBy(997);
return numerator.dividedBy(denominator.plus(1));
}
function getSendType(inputCurrency, outputCurrency) {
if (!inputCurrency || !outputCurrency) {
return;
}
if (inputCurrency === outputCurrency) {
return;
}
if (inputCurrency !== 'ETH' && outputCurrency !== 'ETH') {
return 'TOKEN_TO_TOKEN'
}
if (inputCurrency === 'ETH') {
return 'ETH_TO_TOKEN';
}
if (outputCurrency === 'ETH') {
return 'TOKEN_TO_ETH';
}
return;
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment