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 { drizzleConnect } from 'drizzle-react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
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 deepEqual from 'deep-equal';
import { selectors } from '../../ducks/web3connect';
import Header from '../../components/Header';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import AddressInputPanel from '../../components/AddressInputPanel';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel';
import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import Pending from '../../assets/images/pending.svg';
import {
calculateExchangeRateFromInput,
calculateExchangeRateFromOutput,
sendInput,
sendOutput,
} from '../../helpers/exchange-utils';
import {
isExchangeUnapproved,
approveExchange,
} from '../../helpers/approval-utils';
import {
getTxStatus
} from '../../helpers/contract-utils';
import EXCHANGE_ABI from '../../abi/exchange';
import "./send.scss";
import promisify from "../../helpers/web3-promisfy";
const INPUT = 0;
const OUTPUT = 1;
class Send extends Component {
static propTypes = {
// Injected by React Router Dom
push: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
currentAddress: PropTypes.string,
account: PropTypes.string,
isConnected: PropTypes.bool.isRequired,
updateField: PropTypes.func.isRequired,
input: PropTypes.string,
output: PropTypes.string,
inputCurrency: PropTypes.string,
outputCurrency: PropTypes.string,
recipient: PropTypes.string,
lastEditedField: PropTypes.string,
};
static contextTypes = {
drizzle: PropTypes.object,
selectors: PropTypes.func.isRequired,
web3: PropTypes.object.isRequired,
};
state = {
exchangeRate: BN(0),
approvalTxId: null,
sendTxId: null,
inputValue: '',
outputValue: '',
inputCurrency: '',
outputCurrency: '',
inputAmountB: '',
lastEditedField: '',
recipient: '',
};
shouldComponentUpdate(nextProps, nextState) {
return !deepEqual(nextProps, this.props) ||
!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)}`);
}
});
return true;
}
componentWillUnmount() {
this.resetSend();
reset() {
this.setState({
inputValue: '',
outputValue: '',
inputCurrency: '',
outputCurrency: '',
inputAmountB: '',
lastEditedField: '',
recipient: '',
});
}
resetSend() {
this.props.resetSend();
this.setState({approvalTxId: null, sendTxId: null});
componentWillReceiveProps() {
this.recalcForm();
}
getSendStatus() {
const { drizzle } = this.context;
validate() {
const { selectors, account } = this.props;
const {
inputValue, outputValue,
inputCurrency, outputCurrency,
recipient,
} = this.state;
return getTxStatus({
drizzleCtx: drizzle,
txId: this.state.sendTxId,
});
}
let inputError = '';
let outputError = '';
let isValid = true;
getTokenLabel(address) {
if (address === 'ETH') {
return 'ETH';
if (!inputValue || !outputValue || !inputCurrency || !outputCurrency || !recipient) {
isValid = false;
}
const {
initialized,
contracts,
} = this.props;
const { drizzle } = this.context;
const { web3 } = drizzle;
const { value: inputBalance, decimals: inputDecimals } = selectors().getBalance(account, inputCurrency);
if (!initialized || !web3 || !address) {
return '';
if (inputBalance.isLessThan(BN(inputValue * 10 ** inputDecimals))) {
inputError = 'Insufficient Balance';
}
const symbolKey = drizzle.contracts[address].methods.symbol.cacheCall();
const token = contracts[address];
const symbol = token.symbol[symbolKey];
if (!symbol) {
return '';
if (inputValue === 'N/A') {
inputError = 'Not a valid input value';
}
return symbol.value;
return {
inputError,
outputError,
isValid: isValid && !inputError && !outputError,
};
}
getBalance(currency) {
const { selectors, account } = this.props;
recalcForm() {
const { inputCurrency, outputCurrency } = this.state;
if (!currency) {
return '';
if (!inputCurrency || !outputCurrency) {
return;
}
if (currency === 'ETH') {
const { value, decimals } = selectors().getBalance(account);
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`;
if (inputCurrency === outputCurrency) {
this.setState({
inputValue: '',
outputValue: '',
});
return;
}
const { value, decimals } = selectors().getTokenBalance(currency, account);
return `Balance: ${value.dividedBy(10 ** decimals).toFixed(4)}`;
}
updateInput(amount) {
this.props.updateField('input', amount);
if (!amount) {
this.props.updateField('output', '');
if (inputCurrency !== 'ETH' && outputCurrency !== 'ETH') {
this.recalcTokenTokenForm();
return;
}
this.props.updateField('lastEditedField', 'input');
}
updateOutput(amount) {
this.props.updateField('output', amount);
if (!amount) {
this.props.updateField('input', '');
}
this.props.updateField('lastEditedField', 'output');
this.recalcEthTokenForm();
}
async getExchangeRate(props) {
recalcTokenTokenForm = () => {
const {
input,
output,
exchangeAddresses: { fromToken },
selectors,
} = this.props;
const {
inputValue: oldInputValue,
outputValue: oldOutputValue,
inputCurrency,
outputCurrency,
exchangeAddresses,
lastEditedField,
contracts,
} = props;
const { drizzle } = this.context;
return lastEditedField === 'input'
? await calculateExchangeRateFromInput({
drizzleCtx: drizzle,
contractStore: contracts,
input,
output,
inputCurrency,
outputCurrency,
exchangeAddresses,
})
: await calculateExchangeRateFromOutput({
drizzleCtx: drizzle,
contractStore: contracts,
input,
output,
inputCurrency,
outputCurrency,
exchangeAddresses,
}) ;
}
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 inputAmountA = BN(oldInputValue).multipliedBy(10 ** inputDecimalsA);
const outputAmountA = calculateEtherTokenOutput({
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 {
input,
inputCurrency,
account,
contracts,
exchangeAddresses
} = this.props;
const { drizzle } = this.context;
const exchangeRate = outputAmountB.dividedBy(inputAmountA);
const outputValue = outputAmountB.dividedBy(BN(10 ** outputDecimalsB)).toFixed(7);
return isExchangeUnapproved({
value: input,
currency: inputCurrency,
drizzleCtx: drizzle,
contractStore: contracts,
account,
exchangeAddresses,
});
}
const appendState = {};
approveExchange = async () => {
const {
inputCurrency,
exchangeAddresses,
account,
contracts,
} = this.props;
const { drizzle } = this.context;
if (this.getIsUnapproved()) {
const approvalTxId = await approveExchange({
currency: inputCurrency,
drizzleCtx: drizzle,
contractStore: contracts,
account,
exchangeAddresses,
if (!exchangeRate.isEqualTo(BN(oldExchangeRate))) {
appendState.exchangeRate = exchangeRate;
}
if (outputValue !== oldOutputValue) {
appendState.outputValue = outputValue;
}
this.setState(appendState);
}
if (lastEditedField === OUTPUT) {
if (!oldOutputValue) {
return this.setState({
inputValue: '',
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({
drizzleCtx: drizzle,
txId: this.state.approvalTxId,
});
}
recalcEthTokenForm = () => {
const {
exchangeAddresses: { fromToken },
selectors,
} = this.props;
onSend = async () => {
const {
input,
output,
inputValue: oldInputValue,
outputValue: oldOutputValue,
inputCurrency,
outputCurrency,
recipient,
exchangeAddresses,
lastEditedField,
account,
contracts,
selectors,
} = this.props;
exchangeRate: oldExchangeRate,
} = this.state;
const { drizzle } = this.context;
const { decimals: inputDecimals } = inputCurrency === 'ETH' ?
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,
outputCurrency,
recipient,
exchangeAddresses,
account,
inputDecimals,
outputDecimals,
});
const tokenAddress = [inputCurrency, outputCurrency].filter(currency => currency !== 'ETH')[0];
const exchangeAddress = fromToken[tokenAddress];
if (!exchangeAddress) {
return;
}
if (lastEditedField === 'output') {
sendTxId = await sendOutput({
drizzleCtx: drizzle,
contractStore: contracts,
input,
output,
inputCurrency,
outputCurrency,
recipient,
exchangeAddresses,
account,
inputDecimals,
outputDecimals,
});
const { value: inputReserve, decimals: inputDecimals } = selectors().getBalance(exchangeAddress, inputCurrency);
const { value: outputReserve, decimals: outputDecimals }= selectors().getBalance(exchangeAddress, outputCurrency);
if (lastEditedField === INPUT) {
if (!oldInputValue) {
return this.setState({
outputValue: '',
exchangeRate: BN(0),
});
}
const inputAmount = BN(oldInputValue).multipliedBy(10 ** inputDecimals);
const outputAmount = calculateEtherTokenOutput({ inputAmount, inputReserve, outputReserve });
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 = () => {
if (this.getIsUnapproved() && this.getApprovalStatus() !== 'pending') {
this.approveExchange();
}
}
updateOutput = amount => {
this.setState({
outputValue: amount,
lastEditedField: OUTPUT,
}, this.recalcForm);
};
validate() {
onSend = async () => {
const {
selectors,
exchangeAddresses: { fromToken },
account,
input,
output,
inputCurrency,
outputCurrency
web3,
selectors,
} = this.props;
let inputError;
let isValid = true;
if (!input || !output || !inputCurrency || !outputCurrency) {
isValid = false;
const {
inputValue,
outputValue,
inputCurrency,
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,
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';
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,
)
.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 {
inputError,
outputError: '',
isValid: isValid && !inputError,
};
}
renderSummary(inputError, outputError, inputLabel, outputLabel) {
renderSummary() {
const {
selectors,
input,
output,
inputValue,
inputCurrency,
inputError,
outputValue,
outputCurrency,
outputError,
recipient,
} = this.props;
const { exchangeRate } = this.state;
} = this.state;
const { selectors, account } = this.props;
const { label: inputLabel } = selectors().getBalance(account, inputCurrency);
const { label: outputLabel } = selectors().getBalance(account, outputCurrency);
let nextStepMessage;
if (inputError || outputError) {
......@@ -362,8 +469,8 @@ class Send extends Component {
nextStepMessage = 'Select a token to continue.';
} else if (inputCurrency === outputCurrency) {
nextStepMessage = 'Must be different token.';
} else if (!input || !output) {
const missingCurrencyValue = !input ? inputCurrency : outputCurrency;
} else if (!inputValue || !outputValue) {
const missingCurrencyValue = !inputValue ? inputLabel : outputLabel;
nextStepMessage = `Enter a ${missingCurrencyValue} value to continue.`;
} else if (!recipient) {
nextStepMessage = 'Enter a wallet address to send to.';
......@@ -378,34 +485,69 @@ class Send extends Component {
}
const SLIPPAGE = 0.025;
const exchangedOutput = BN(input).multipliedBy(BN(exchangeRate));
const minOutput = exchangedOutput.multipliedBy(1 - SLIPPAGE).toFixed(8);
const maxOutput = exchangedOutput.multipliedBy(1 + SLIPPAGE).toFixed(8);
const minOutput = BN(outputValue).multipliedBy(1 - SLIPPAGE).toFixed(2);
const maxOutput = BN(outputValue).multipliedBy(1 + SLIPPAGE).toFixed(2);
return (
<div className="swap__summary-wrapper">
<div>
You are selling {b(`${input} ${inputLabel}`)}
You are selling {b(`${inputValue} ${inputLabel}`)}
</div>
<div className="send__last-summary-text">
<span className="swap__highlight-text">{recipient.slice(0, 6)}</span>
will receive between {b(`${minOutput} ${outputLabel}`)} and {b(`${maxOutput} ${outputLabel}`)}
<span className="swap__highlight-text">{recipient.slice(0, 6)}</span> will receive between {b(`${minOutput} ${outputLabel}`)} and {b(`${maxOutput} ${outputLabel}`)}
</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() {
const { lastEditedField, inputCurrency, outputCurrency, input, output, recipient } = this.props;
const { exchangeRate } = this.state;
const inputLabel = this.getTokenLabel(inputCurrency);
const outputLabel = this.getTokenLabel(outputCurrency);
const { selectors, account } = this.props;
const {
lastEditedField,
inputCurrency,
outputCurrency,
inputValue,
outputValue,
recipient,
} = this.state;
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();
return (
<div className="send">
<div className="swap">
<Header />
<div
className={classnames('swap__content', {
......@@ -414,17 +556,17 @@ class Send extends Component {
>
<CurrencyInputPanel
title="Input"
description={lastEditedField === 'output' ? estimatedText : ''}
onCurrencySelected={(d) => {
this.props.updateField('inputCurrency', d)
this.props.sync();
}}
onValueChange={d => this.updateInput(d)}
description={lastEditedField === OUTPUT ? estimatedText : ''}
extraText={inputCurrency
? `Balance: ${inputBalance.dividedBy(BN(10 ** inputDecimals)).toFixed(4)}`
: ''
}
onCurrencySelected={inputCurrency => this.setState({ inputCurrency }, this.recalcForm)}
onValueChange={this.updateInput}
selectedTokens={[inputCurrency, outputCurrency]}
errorMessage={inputError}
value={input}
selectedTokenAddress={inputCurrency}
extraText={this.getBalance(inputCurrency)}
value={inputValue}
errorMessage={inputError}
/>
<OversizedPanel>
<div className="swap__down-arrow-background">
......@@ -433,17 +575,17 @@ class Send extends Component {
</OversizedPanel>
<CurrencyInputPanel
title="Output"
description={lastEditedField === 'input' ? estimatedText : ''}
onCurrencySelected={(d) => {
this.props.updateField('outputCurrency', d)
this.props.sync();
}}
onValueChange={d => this.updateOutput(d)}
description={lastEditedField === INPUT ? estimatedText : ''}
extraText={outputCurrency
? `Balance: ${outputBalance.dividedBy(BN(10 ** outputDecimals)).toFixed(4)}`
: ''
}
onCurrencySelected={outputCurrency => this.setState({ outputCurrency }, this.recalcForm)}
onValueChange={this.updateOutput}
selectedTokens={[inputCurrency, outputCurrency]}
errorMessage={outputError}
value={output}
value={outputValue}
selectedTokenAddress={outputCurrency}
extraText={this.getBalance(outputCurrency)}
errorMessage={outputError}
disableUnlock
/>
<OversizedPanel>
......@@ -453,71 +595,90 @@ class Send extends Component {
</OversizedPanel>
<AddressInputPanel
value={recipient}
onChange={address => this.props.updateField('recipient', address)}
onChange={address => this.setState({recipient: address})}
/>
<OversizedPanel hideBottom>
<div className="swap__exchange-rate-wrapper">
<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) }
{ this.renderExchangeRate() }
{ this.renderSummary() }
</div>
<button
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
'swap__cta-btn--inactive': !isValid,
})}
disabled={!isValid}
onClick={this.onSend}
>
Send
</button>
</div>
);
}
}
export default withRouter(
drizzleConnect(
Send,
(state, ownProps) => ({
// 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,
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()),
})
),
export default drizzleConnect(
Send,
state => ({
balances: state.web3connect.balances,
isConnected: !!state.web3connect.account,
account: state.web3connect.account,
web3: state.web3connect.web3,
exchangeAddresses: state.addresses.exchangeAddresses,
}),
dispatch => ({
selectors: () => dispatch(selectors()),
}),
);
function b(text) {
return <span className="swap__highlight-text">{text}</span>
const b = text => <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