Commit 9fc59c4f authored by Kenny Tran's avatar Kenny Tran

Refactor send to use web3connect

parent a28d0f0b
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); }
reset() {
this.setState({
inputValue: '',
outputValue: '',
inputCurrency: '',
outputCurrency: '',
inputAmountB: '',
lastEditedField: '',
recipient: '',
});
} }
componentDidUpdate() { componentWillReceiveProps() {
if (this.getSendStatus() === 'pending') { this.recalcForm();
this.resetSend();
} }
this.getExchangeRate(this.props) validate() {
.then(exchangeRate => { const { selectors, account } = this.props;
if (this.state.exchangeRate !== exchangeRate) { const {
this.setState({ exchangeRate }); inputValue, outputValue,
} inputCurrency, outputCurrency,
} = this.state;
if (!exchangeRate) { let inputError = '';
return; let outputError = '';
} let isValid = true;
if (this.props.lastEditedField === 'input') { if (!inputValue || !outputValue || !inputCurrency || !outputCurrency) {
this.props.updateField('output', `${BN(this.props.input).multipliedBy(exchangeRate).toFixed(7)}`); isValid = false;
} else if (this.props.lastEditedField === 'output') {
this.props.updateField('input', `${BN(this.props.output).multipliedBy(BN(1).dividedBy(exchangeRate)).toFixed(7)}`);
} }
});
const { value: inputBalance, decimals: inputDecimals } = selectors().getBalance(account, inputCurrency);
if (inputBalance.isLessThan(BN(inputValue * 10 ** inputDecimals))) {
inputError = 'Insufficient Balance';
} }
componentWillUnmount() { if (inputValue === 'N/A') {
this.resetSend(); inputError = 'Not a valid input value';
} }
resetSend() { return {
this.props.resetSend(); inputError,
this.setState({approvalTxId: null, sendTxId: null}); outputError,
isValid: isValid && !inputError && !outputError,
};
} }
getSendStatus() { recalcForm() {
const { drizzle } = this.context; const { inputCurrency, outputCurrency } = this.state;
if (!inputCurrency || !outputCurrency) {
return;
}
return getTxStatus({ if (inputCurrency === outputCurrency) {
drizzleCtx: drizzle, this.setState({
txId: this.state.sendTxId, inputValue: '',
outputValue: '',
}); });
return;
}
if (inputCurrency !== 'ETH' && outputCurrency !== 'ETH') {
this.recalcTokenTokenForm();
return;
} }
getTokenLabel(address) { this.recalcEthTokenForm();
if (address === 'ETH') {
return 'ETH';
} }
recalcTokenTokenForm = () => {
const { const {
initialized, exchangeAddresses: { fromToken },
contracts, selectors,
} = this.props; } = this.props;
const { drizzle } = this.context;
const { web3 } = drizzle;
if (!initialized || !web3 || !address) { const {
return ''; inputValue: oldInputValue,
outputValue: oldOutputValue,
inputCurrency,
outputCurrency,
lastEditedField,
exchangeRate: oldExchangeRate,
inputAmountB: oldInputAmountB,
} = this.state;
const exchangeAddressA = fromToken[inputCurrency];
const exchangeAddressB = fromToken[outputCurrency];
const { value: inputReserveA, decimals: inputDecimalsA } = selectors().getBalance(exchangeAddressA, inputCurrency);
const { value: outputReserveA }= selectors().getBalance(exchangeAddressA, 'ETH');
const { value: inputReserveB } = selectors().getBalance(exchangeAddressB, 'ETH');
const { value: outputReserveB, decimals: outputDecimalsB }= selectors().getBalance(exchangeAddressB, outputCurrency);
if (lastEditedField === INPUT) {
if (!oldInputValue) {
return this.setState({
outputValue: '',
exchangeRate: BN(0),
});
} }
const symbolKey = drizzle.contracts[address].methods.symbol.cacheCall(); const inputAmountA = BN(oldInputValue).multipliedBy(10 ** inputDecimalsA);
const token = contracts[address]; const outputAmountA = calculateEtherTokenOutput({
const symbol = token.symbol[symbolKey]; 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,
});
if (!symbol) { const exchangeRate = outputAmountB.dividedBy(inputAmountA);
return ''; const outputValue = outputAmountB.dividedBy(BN(10 ** outputDecimalsB)).toFixed(7);
}
return symbol.value; const appendState = {};
}
getBalance(currency) { if (!exchangeRate.isEqualTo(BN(oldExchangeRate))) {
const { selectors, account } = this.props; appendState.exchangeRate = exchangeRate;
}
if (!currency) { if (outputValue !== oldOutputValue) {
return ''; appendState.outputValue = outputValue;
} }
if (currency === 'ETH') { this.setState(appendState);
const { value, decimals } = selectors().getBalance(account);
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`;
} }
const { value, decimals } = selectors().getTokenBalance(currency, account); if (lastEditedField === OUTPUT) {
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`; if (!oldOutputValue) {
return this.setState({
inputValue: '',
exchangeRate: BN(0),
});
} }
updateInput(amount) { const outputAmountB = BN(oldOutputValue).multipliedBy(10 ** outputDecimalsB);
this.props.updateField('input', amount); const inputAmountB = calculateEtherTokenInput({
if (!amount) { outputAmount: outputAmountB,
this.props.updateField('output', ''); 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,
});
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;
} }
this.props.updateField('lastEditedField', 'input');
if (inputValue !== oldInputValue) {
appendState.inputValue = inputValue;
} }
updateOutput(amount) { if (!inputAmountB.isEqualTo(BN(oldInputAmountB))) {
this.props.updateField('output', amount); appendState.inputAmountB = inputAmountB;
if (!amount) {
this.props.updateField('input', '');
} }
this.props.updateField('lastEditedField', 'output');
this.setState(appendState);
} }
async getExchangeRate(props) { };
recalcEthTokenForm = () => {
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; } = this.state;
const { drizzle } = this.context; const tokenAddress = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0];
const exchangeAddress = fromToken[tokenAddress];
if (!exchangeAddress) {
return;
}
const { value: inputReserve, decimals: inputDecimals } = selectors().getBalance(exchangeAddress, inputCurrency);
const { value: outputReserve, decimals: outputDecimals }= selectors().getBalance(exchangeAddress, outputCurrency);
return lastEditedField === 'input' if (lastEditedField === INPUT) {
? await calculateExchangeRateFromInput({ if (!oldInputValue) {
drizzleCtx: drizzle, return this.setState({
contractStore: contracts, outputValue: '',
input, exchangeRate: BN(0),
output, });
inputCurrency,
outputCurrency,
exchangeAddresses,
})
: await calculateExchangeRateFromOutput({
drizzleCtx: drizzle,
contractStore: contracts,
input,
output,
inputCurrency,
outputCurrency,
exchangeAddresses,
}) ;
} }
getIsUnapproved() { const inputAmount = BN(oldInputValue).multipliedBy(10 ** inputDecimals);
const { const outputAmount = calculateEtherTokenOutput({ inputAmount, inputReserve, outputReserve });
input, const exchangeRate = outputAmount.dividedBy(inputAmount);
inputCurrency, const outputValue = outputAmount.dividedBy(BN(10 ** outputDecimals)).toFixed(7);
account,
contracts,
exchangeAddresses
} = this.props;
const { drizzle } = this.context;
return isExchangeUnapproved({ const appendState = {};
value: input,
currency: inputCurrency, if (!exchangeRate.isEqualTo(BN(oldExchangeRate))) {
drizzleCtx: drizzle, appendState.exchangeRate = exchangeRate;
contractStore: contracts,
account,
exchangeAddresses,
});
} }
approveExchange = async () => { if (outputValue !== oldOutputValue) {
const { appendState.outputValue = outputValue;
inputCurrency, }
exchangeAddresses,
account,
contracts,
} = this.props;
const { drizzle } = this.context;
if (this.getIsUnapproved()) { this.setState(appendState);
const approvalTxId = await approveExchange({ } else if (lastEditedField === OUTPUT) {
currency: inputCurrency, if (!oldOutputValue) {
drizzleCtx: drizzle, return this.setState({
contractStore: contracts, inputValue: '',
account, exchangeRate: BN(0),
exchangeAddresses,
}); });
this.setState({ approvalTxId })
} }
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;
} }
getApprovalStatus() { if (inputValue !== oldInputValue) {
const { drizzle } = this.context; appendState.inputValue = inputValue;
}
return getTxStatus({ this.setState(appendState);
drizzleCtx: drizzle,
txId: this.state.approvalTxId,
});
} }
};
updateInput = amount => {
this.setState({
inputValue: amount,
lastEditedField: INPUT,
}, this.recalcForm);
};
updateOutput = amount => {
this.setState({
outputValue: amount,
lastEditedField: OUTPUT,
}, this.recalcForm);
};
onSend = async () => { onSend = async () => {
const { const {
input, exchangeAddresses: { fromToken },
output,
inputCurrency,
outputCurrency,
recipient,
exchangeAddresses,
lastEditedField,
account, account,
contracts, web3,
selectors, selectors,
} = this.props; } = this.props;
const {
const { drizzle } = this.context; inputValue,
const { decimals: inputDecimals } = inputCurrency === 'ETH' ? outputValue,
selectors().getBalance(account)
: selectors().getTokenBalance(inputCurrency, account);
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, inputCurrency,
outputCurrency, outputCurrency,
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, recipient,
exchangeAddresses,
account,
inputDecimals,
outputDecimals,
});
}
if (lastEditedField === 'output') {
sendTxId = await sendOutput({
drizzleCtx: drizzle,
contractStore: contracts,
input,
output,
inputCurrency,
outputCurrency, outputCurrency,
)
.send({
from: account,
}, err => !err && this.reset());
break;
default:
break;
}
}
if (lastEditedField === OUTPUT) {
// send output
switch (type) {
case 'ETH_TO_TOKEN':
new web3.eth.Contract(EXCHANGE_ABI, fromToken[outputCurrency])
.methods
.ethToTokenTransferOutput(
BN(outputValue).multipliedBy(10 ** outputDecimals).toFixed(0),
deadline,
recipient, recipient,
exchangeAddresses, )
account, .send({
inputDecimals, from: account,
outputDecimals, value: BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0),
}); }, err => !err && this.reset());
} break;
case 'TOKEN_TO_ETH':
this.setState({ sendTxId }); new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency])
}; .methods
.tokenToEthTransferOutput(
handleSubButtonClick = () => { BN(outputValue).multipliedBy(10 ** outputDecimals).toFixed(0),
if (this.getIsUnapproved() && this.getApprovalStatus() !== 'pending') { BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + ALLOWED_SLIPPAGE).toFixed(0),
this.approveExchange(); deadline,
} recipient,
)
.send({
from: account,
}, err => !err && this.reset());
break;
case 'TOKEN_TO_TOKEN':
if (!inputAmountB) {
return;
} }
validate() { console.log(
const { BN(outputValue).multipliedBy(10 ** outputDecimals).toFixed(0),
selectors, BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + TOKEN_ALLOWED_SLIPPAGE).toFixed(0),
account, inputAmountB.multipliedBy(1.2).toFixed(0),
input, deadline,
output, outputCurrency,
inputCurrency, )
outputCurrency new web3.eth.Contract(EXCHANGE_ABI, fromToken[inputCurrency])
} = this.props; .methods
.tokenToTokenTransferOutput(
let inputError; BN(outputValue).multipliedBy(10 ** outputDecimals).toFixed(0),
let isValid = true; BN(inputValue).multipliedBy(10 ** inputDecimals).multipliedBy(1 + TOKEN_ALLOWED_SLIPPAGE).toFixed(0),
inputAmountB.multipliedBy(1.2).toFixed(0),
if (!input || !output || !inputCurrency || !outputCurrency) { deadline,
isValid = false; recipient,
outputCurrency,
).send({
from: account,
}, err => !err && this.reset());
break;
default:
break;
} }
const { value: inputBalance, decimals: inputDecimals } = inputCurrency === 'ETH' ?
selectors().getBalance(account)
: selectors().getTokenBalance(inputCurrency, account);
if (inputBalance.isLessThan(BN(input * 10 ** inputDecimals))) {
inputError = 'Insufficient Balance';
} }
return {
inputError,
outputError: '',
isValid: isValid && !inputError,
}; };
}
renderSummary(inputError, outputError, inputLabel, outputLabel) { renderSummary() {
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 +468,8 @@ class Send extends Component { ...@@ -362,8 +468,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 +484,69 @@ class Send extends Component { ...@@ -378,34 +484,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 +555,17 @@ class Send extends Component { ...@@ -414,17 +555,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 +574,17 @@ class Send extends Component { ...@@ -433,17 +574,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 +594,90 @@ class Send extends Component { ...@@ -453,71 +594,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, ownProps) => ({ state => ({
// React Router
push: ownProps.history.push,
pathname: ownProps.location.pathname,
// From Drizzle
initialized: state.drizzleStatus.initialized,
balance: state.accountBalances[state.accounts[0]] || null,
account: state.accounts[0],
contracts: state.contracts,
currentAddress: state.accounts[0],
isConnected: !!(state.drizzleStatus.initialized && state.accounts[0]),
// Redux Store
balances: state.web3connect.balances, balances: state.web3connect.balances,
input: state.send.input, isConnected: !!state.web3connect.account,
output: state.send.output, account: state.web3connect.account,
inputCurrency: state.send.inputCurrency, web3: state.web3connect.web3,
outputCurrency: state.send.outputCurrency,
recipient: state.send.recipient,
lastEditedField: state.send.lastEditedField,
exchangeAddresses: state.addresses.exchangeAddresses, exchangeAddresses: state.addresses.exchangeAddresses,
}), }),
dispatch => ({ 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()), 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