Add all smart trade methods to swap (#45)

* wip

* ETH to ERC20 swap input

* ERC20 to ETH swap input

* Add ERC20 to ETH swapInput

* Add ETH to ERC20 swapOutput

* Add ERC20 to ETH swapOutput

* Add ERC20 to ERC20 swapInput

* Add ERC20 to ERC20 swapOutput

* Remove console.log
This commit is contained in:
Chi Kei Chan 2018-10-17 01:43:13 -07:00 committed by GitHub
parent b6ad0dbb3e
commit c465d2d0d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 424 additions and 52 deletions

File diff suppressed because one or more lines are too long

@ -103,7 +103,7 @@ class CurrencyInputPanel extends Component {
}
if (selectedTokenAddress === 'ETH') {
return `Balance: ${web3.utils.fromWei(balance, 'ether')}`;
return `Balance: ${BN(web3.utils.fromWei(balance, 'ether')).toFixed(2)}`;
}
const tokenData = this.getTokenData(selectedTokenAddress);
@ -145,32 +145,36 @@ class CurrencyInputPanel extends Component {
this.props.onCurrencySelected(address);
if (address && address !== 'ETH') {
// Add Token Contract
const { drizzle } = this.context;
const { fromToken } = this.props.exchangeAddresses;
const { web3 } = drizzle;
const tokenConfig = {
contractName: address,
web3Contract: new web3.eth.Contract(ERC20_ABI, address),
};
const tokenEvents = ['Approval', 'Transfer'];
this.context.drizzle.addContract(tokenConfig, tokenEvents, { from: this.props.account });
// Add Token Contract
if (!this.props.contracts[address]) {
// console.log(`Adding Token Contract - ${address}`);
const tokenConfig = {
contractName: address,
web3Contract: new web3.eth.Contract(ERC20_ABI, address),
};
const tokenEvents = ['Approval', 'Transfer'];
this.context.drizzle.addContract(tokenConfig, tokenEvents, { from: this.props.account });
}
// Add Exchange Contract
const exchangeAddress = fromToken[address];
if (!exchangeAddress) {
return;
}
const exchangeConfig = {
contractName: exchangeAddress,
web3Contract: new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress),
};
const exchangeEvents = ['Approval', 'Transfer', 'TokenPurchase', 'EthPurchase', 'AddLiquidity', 'RemoveLiquidity'];
this.context.drizzle.addContract(exchangeConfig, exchangeEvents , { from: this.props.account });
if (!this.props.contracts[exchangeAddress]) {
// console.log(`Adding Exchange Contract - ${exchangeAddress}`);
const exchangeConfig = {
contractName: exchangeAddress,
web3Contract: new web3.eth.Contract(EXCHANGE_ABI, exchangeAddress),
};
const exchangeEvents = ['Approval', 'Transfer', 'TokenPurchase', 'EthPurchase', 'AddLiquidity', 'RemoveLiquidity'];
this.context.drizzle.addContract(exchangeConfig, exchangeEvents , { from: this.props.account });
}
}
};

@ -1,4 +1,5 @@
import {BigNumber as BN} from "bignumber.js";
import promisify from "./web3-promisfy";
export const calculateExchangeRateFromInput = async opts => {
const { inputCurrency, outputCurrency } = opts;
@ -47,6 +48,53 @@ export const calculateExchangeRateFromOutput = async opts => {
return ERC20_TO_ERC20.calculateInput(opts);
};
export const swapInput = async opts => {
const { inputCurrency, outputCurrency } = opts;
if (!inputCurrency || !outputCurrency) {
return;
}
if (inputCurrency === outputCurrency) {
console.error(`Input and Output currency cannot be the same`);
return;
}
if (inputCurrency === 'ETH' && outputCurrency !== 'ETH') {
return ETH_TO_ERC20.swapInput(opts);
}
if (outputCurrency === 'ETH' && inputCurrency !== 'ETH') {
return ERC20_TO_ETH.swapInput(opts);
}
return ERC20_TO_ERC20.swapInput(opts);
};
export const swapOutput = async opts => {
const { inputCurrency, outputCurrency } = opts;
if (!inputCurrency || !outputCurrency) {
return;
}
if (inputCurrency === outputCurrency) {
console.error(`Input and Output currency cannot be the same`);
return;
}
if (inputCurrency === 'ETH' && outputCurrency !== 'ETH') {
return ETH_TO_ERC20.swapOutput(opts);
}
if (outputCurrency === 'ETH' && inputCurrency !== 'ETH') {
return ERC20_TO_ETH.swapOutput(opts);
}
return ERC20_TO_ERC20.swapOutput(opts);
};
const ETH_TO_ERC20 = {
calculateOutput: async ({drizzleCtx, contractStore, input, inputCurrency, outputCurrency, exchangeAddresses }) => {
if (inputCurrency !== 'ETH') {
@ -137,6 +185,73 @@ const ETH_TO_ERC20 = {
return exchangeRate;
},
swapInput: async ({drizzleCtx, contractStore, input, output, account, inputCurrency, outputCurrency, exchangeAddresses }) => {
if (inputCurrency !== 'ETH') {
console.error('Input Currency should be ETH');
return;
}
if (!outputCurrency || outputCurrency === 'ETH') {
console.error('Output Currency should be ERC20');
return;
}
const exchangeAddress = exchangeAddresses.fromToken[outputCurrency];
const exchange = drizzleCtx.contracts[exchangeAddress];
if (!exchangeAddress || !exchange) {
console.error(`Cannot find Exchange Address for ${outputCurrency}`);
return;
}
const { web3 } = drizzleCtx;
const blockNumber = await promisify(web3, 'getBlockNumber');
const block = await promisify(web3, 'getBlock', blockNumber);
const deadline = block.timestamp + 300;
const ALLOWED_SLIPPAGE = BN(0.025);
const outputDecimals = await getDecimals({ address: outputCurrency, contractStore, drizzleCtx });
const minOutput = BN(output).multipliedBy(10 ** outputDecimals).multipliedBy(BN(1).minus(ALLOWED_SLIPPAGE));
exchange.methods.ethToTokenSwapInput.cacheSend(minOutput.toFixed(0), deadline, {
from: account,
value: BN(input).multipliedBy(10 ** 18).toFixed(0),
});
},
swapOutput: async ({drizzleCtx, contractStore, input, output, account, inputCurrency, outputCurrency, exchangeAddresses }) => {
if (inputCurrency !== 'ETH') {
console.error('Input Currency should be ETH');
return;
}
if (!outputCurrency || outputCurrency === 'ETH') {
console.error('Output Currency should be ERC20');
return;
}
const exchangeAddress = exchangeAddresses.fromToken[outputCurrency];
const exchange = drizzleCtx.contracts[exchangeAddress];
if (!exchangeAddress || !exchange) {
console.error(`Cannot find Exchange Address for ${outputCurrency}`);
return;
}
const { web3 } = drizzleCtx;
const blockNumber = await promisify(web3, 'getBlockNumber');
const block = await promisify(web3, 'getBlock', blockNumber);
const deadline = block.timestamp + 300;
const ALLOWED_SLIPPAGE = BN(0.025);
const outputDecimals = await getDecimals({ address: outputCurrency, contractStore, drizzleCtx });
const outputAmount = BN(output).multipliedBy(BN(10 ** outputDecimals));
const maxInput = BN(input).multipliedBy(10 ** 18).multipliedBy(BN(1).plus(ALLOWED_SLIPPAGE));
exchange.methods.ethToTokenSwapOutput.cacheSend(outputAmount.toFixed(0), deadline, {
from: account,
value: maxInput.toFixed(0),
});
},
};
const ERC20_TO_ETH = {
@ -192,7 +307,7 @@ const ERC20_TO_ETH = {
}
if (!inputCurrency || inputCurrency === 'ETH') {
console.error('Output Currency should be ERC20');
console.error('Input Currency should be ERC20');
return;
}
@ -229,6 +344,78 @@ const ERC20_TO_ETH = {
return exchangeRate;
},
swapInput: async ({drizzleCtx, contractStore, input, output, account, inputCurrency, outputCurrency, exchangeAddresses }) => {
if (outputCurrency !== 'ETH') {
console.error('Output Currency should be ETH');
return;
}
if (!inputCurrency || inputCurrency === 'ETH') {
console.error('Input Currency should be ERC20');
return;
}
const exchangeAddress = exchangeAddresses.fromToken[inputCurrency];
const exchange = drizzleCtx.contracts[exchangeAddress];
if (!exchangeAddress || !exchange) {
console.error(`Cannot find Exchange Address for ${inputCurrency}`);
return;
}
const { web3 } = drizzleCtx;
const blockNumber = await promisify(web3, 'getBlockNumber');
const block = await promisify(web3, 'getBlock', blockNumber);
const deadline = block.timestamp + 300;
const ALLOWED_SLIPPAGE = BN(0.025);
const inputDecimals = await getDecimals({ address: inputCurrency, contractStore, drizzleCtx });
const minOutput = BN(output).multipliedBy(10 ** 18).multipliedBy(BN(1).minus(ALLOWED_SLIPPAGE));
const inputAmount = BN(input).multipliedBy(10 ** inputDecimals);
exchange.methods.tokenToEthSwapInput.cacheSend(
inputAmount.toFixed(0),
minOutput.toFixed(0),
deadline,
{ from: account, value: '0x0' },
);
},
swapOutput: async ({drizzleCtx, contractStore, input, output, account, inputCurrency, outputCurrency, exchangeAddresses }) => {
if (outputCurrency !== 'ETH') {
console.error('Output Currency should be ETH');
return;
}
if (!inputCurrency || inputCurrency === 'ETH') {
console.error('Output Currency should be ERC20');
return;
}
const exchangeAddress = exchangeAddresses.fromToken[inputCurrency];
const exchange = drizzleCtx.contracts[exchangeAddress];
if (!exchangeAddress || !exchange) {
console.error(`Cannot find Exchange Address for ${inputCurrency}`);
return;
}
const { web3 } = drizzleCtx;
const blockNumber = await promisify(web3, 'getBlockNumber');
const block = await promisify(web3, 'getBlock', blockNumber);
const deadline = block.timestamp + 300;
const ALLOWED_SLIPPAGE = BN(0.025);
const inputDecimals = await getDecimals({ address: inputCurrency, contractStore, drizzleCtx });
const maxInput = BN(input).multipliedBy(10 ** inputDecimals).multipliedBy(BN(1).plus(ALLOWED_SLIPPAGE));
const outputAmount = BN(output).multipliedBy(10 ** 18);
exchange.methods.tokenToEthSwapOutput.cacheSend(
outputAmount.toFixed(0),
maxInput.toFixed(0),
deadline,
{ from: account },
);
},
};
const ERC20_TO_ERC20 = {
@ -278,10 +465,112 @@ const ERC20_TO_ERC20 = {
return exchangeRateA.multipliedBy(exchangeRateB);
},
swapInput: async ({drizzleCtx, contractStore, input, output, account, inputCurrency, outputCurrency, exchangeAddresses }) => {
if (!outputCurrency || outputCurrency === 'ETH') {
console.error('Output Currency should be ERC20');
return;
}
if (!inputCurrency || inputCurrency === 'ETH') {
console.error('Input Currency should be ERC20');
return;
}
const exchangeAddress = exchangeAddresses.fromToken[inputCurrency];
const exchange = drizzleCtx.contracts[exchangeAddress];
if (!exchangeAddress || !exchange) {
console.error(`Cannot find Exchange Address for ${inputCurrency}`);
return;
}
const { web3 } = drizzleCtx;
const blockNumber = await promisify(web3, 'getBlockNumber');
const block = await promisify(web3, 'getBlock', blockNumber);
const deadline = block.timestamp + 300;
const ALLOWED_SLIPPAGE = BN(0.04);
const inputDecimals = await getDecimals({ address: inputCurrency, contractStore, drizzleCtx });
const outputDecimals = await getDecimals({ address: outputCurrency, contractStore, drizzleCtx });
const inputAmount = BN(input).multipliedBy(BN(10 ** inputDecimals));
const outputAmount = BN(input).multipliedBy(BN(10 ** outputDecimals));
const tokenAddress = outputCurrency;
const tokensSold = inputAmount.toFixed(0);
const minTokensBought = outputAmount.multipliedBy(BN(1).plus(ALLOWED_SLIPPAGE)).toFixed(0);
const minEthBought = 1;
exchange.methods.tokenToTokenSwapInput.cacheSend(
tokensSold,
minTokensBought,
minEthBought,
deadline,
tokenAddress,
{ from: account },
);
},
swapOutput: async opts => {
const {
drizzleCtx,
contractStore,
input,
output,
account,
inputCurrency,
outputCurrency,
exchangeAddresses
} = opts;
const exchangeRateA = await ETH_TO_ERC20.calculateInput({ ...opts, inputCurrency: 'ETH' });
if (!exchangeRateA) {
return;
}
if (!outputCurrency || outputCurrency === 'ETH') {
console.error('Output Currency should be ERC20');
return;
}
if (!inputCurrency || inputCurrency === 'ETH') {
console.error('Input Currency should be ERC20');
return;
}
const exchangeAddress = exchangeAddresses.fromToken[inputCurrency];
const exchange = drizzleCtx.contracts[exchangeAddress];
if (!exchangeAddress || !exchange) {
console.error(`Cannot find Exchange Address for ${inputCurrency}`);
return;
}
const { web3 } = drizzleCtx;
const blockNumber = await promisify(web3, 'getBlockNumber');
const block = await promisify(web3, 'getBlock', blockNumber);
const deadline = block.timestamp + 300;
const ALLOWED_SLIPPAGE = BN(0.04);
const inputDecimals = await getDecimals({ address: inputCurrency, contractStore, drizzleCtx });
const outputDecimals = await getDecimals({ address: outputCurrency, contractStore, drizzleCtx });
const inputAmount = BN(input).multipliedBy(BN(10 ** inputDecimals));
const outputAmount = BN(output).multipliedBy(BN(10 ** outputDecimals));
const inputAmountB = BN(output).dividedBy(exchangeRateA).multipliedBy(BN(10 ** 18));
const tokenAddress = outputCurrency;
const tokensBought = outputAmount.toFixed(0);
const maxTokensSold = inputAmount.multipliedBy(BN(1).plus(ALLOWED_SLIPPAGE)).toFixed(0);
const maxEthSold = inputAmountB.multipliedBy(1.2).toFixed(0);
exchange.methods.tokenToTokenSwapOutput.cacheSend(
tokensBought,
maxTokensSold,
maxEthSold,
deadline,
tokenAddress,
{ from: account },
);
},
};
function getDecimals({ address, drizzleCtx, contractStore }) {
return new Promise(async (resolve, reject) => {
if (address === 'ETH') {
@ -295,6 +584,8 @@ function getDecimals({ address, drizzleCtx, contractStore }) {
});
}
const BALANCE_KEY = {};
function getBalance({ currency, address, drizzleCtx, contractStore }) {
return new Promise(async (resolve, reject) => {
if (currency === 'ETH') {
@ -310,7 +601,14 @@ function getBalance({ currency, address, drizzleCtx, contractStore }) {
if (!token) {
return;
}
const balanceKey = token.methods.balanceOf.cacheCall(address);
let balanceKey = BALANCE_KEY[address];
if (!balanceKey) {
balanceKey = token.methods.balanceOf.cacheCall(address);
BALANCE_KEY[address] = balanceKey;
}
const tokenStore = contractStore[currency];
if (!tokenStore) {

@ -0,0 +1,24 @@
export default function promisify(web3, methodName, ...args) {
return new Promise((resolve, reject) => {
if (!web3) {
reject(new Error('No Web3 object'));
return;
}
const method = web3.eth[methodName];
if (!method) {
reject(new Error(`Cannot find web3.eth.${methodName}`));
return;
}
method(...args, (error, data) => {
if (error) {
reject(error);
return;
}
resolve(data);
})
});
}

@ -6,9 +6,6 @@ import store from './store';
import './index.scss';
import registerServiceWorker from './registerServiceWorker';
window.addEventListener('load', function() {
ReactDOM.render(
<DrizzleProvider options={{
@ -20,7 +17,5 @@ window.addEventListener('load', function() {
</DrizzleProvider>
, document.getElementById('root')
);
registerServiceWorker();
});

@ -9,7 +9,13 @@ import Header from '../../components/Header';
import CurrencyInputPanel from '../../components/CurrencyInputPanel';
import OversizedPanel from '../../components/OversizedPanel';
import ArrowDown from '../../assets/images/arrow-down-blue.svg';
import { calculateExchangeRateFromInput, calculateExchangeRateFromOutput } from '../../helpers/exchange-utils';
import {
calculateExchangeRateFromInput,
calculateExchangeRateFromOutput,
swapInput,
swapOutput,
} from '../../helpers/exchange-utils';
import promisify from '../../helpers/web3-promisfy';
import "./swap.scss";
@ -36,6 +42,30 @@ class Swap extends Component {
exchangeRate: BN(0),
};
componentWillReceiveProps(nextProps) {
this.getExchangeRate(nextProps)
.then(exchangeRate => {
this.setState({ exchangeRate });
if (!exchangeRate) {
return;
}
if (nextProps.lastEditedField === 'input') {
this.props.updateField('output', `${BN(nextProps.input).multipliedBy(exchangeRate).toFixed(7)}`);
} else if (nextProps.lastEditedField === 'output') {
this.props.updateField('input', `${BN(nextProps.output).multipliedBy(BN(1).dividedBy(exchangeRate)).toFixed(7)}`);
}
});
}
componentWillUnmount() {
this.props.updateField('output', '');
this.props.updateField('input', '');
this.props.updateField('outputCurrency', '');
this.props.updateField('inputCurrency', '');
this.props.updateField('lastEditedField', '');
}
getTokenLabel(address) {
if (address === 'ETH') {
return 'ETH';
@ -113,41 +143,60 @@ class Swap extends Component {
}) ;
}
componentWillReceiveProps(nextProps) {
this.getExchangeRate(nextProps)
.then(exchangeRate => {
this.setState({ exchangeRate });
if (!exchangeRate) {
return;
}
onSwap = async () => {
const {
input,
output,
inputCurrency,
outputCurrency,
exchangeAddresses,
lastEditedField,
account,
contracts,
} = this.props;
if (nextProps.lastEditedField === 'input') {
this.props.updateField('output', `${BN(nextProps.input).multipliedBy(exchangeRate).toFixed(7)}`);
} else if (nextProps.lastEditedField === 'output') {
this.props.updateField('input', `${BN(nextProps.output).multipliedBy(BN(1).dividedBy(exchangeRate)).toFixed(7)}`);
}
const { drizzle } = this.context;
if (lastEditedField === 'input') {
swapInput({
drizzleCtx: drizzle,
contractStore: contracts,
input,
output,
inputCurrency,
outputCurrency,
exchangeAddresses,
account,
});
}
}
componentWillUnmount() {
this.props.updateField('output', '');
this.props.updateField('input', '');
this.props.updateField('outputCurrency', '');
this.props.updateField('inputCurrency', '');
this.props.updateField('lastEditedField', '');
}
onCurrencySelected(field, data) {
this.props.updateField(field, data);
// this.props
}
if (lastEditedField === 'output') {
swapOutput({
drizzleCtx: drizzle,
contractStore: contracts,
input,
output,
inputCurrency,
outputCurrency,
exchangeAddresses,
account,
});
}
// this.context.drizzle.web3.eth.getBlockNumber((_, d) => this.context.drizzle.web3.eth.getBlock(d, (_,d) => {
// const deadline = d.timestamp + 300;
// const id = exchange.methods.ethToTokenSwapInput.cacheSend(`${output * 10 ** 18}`, deadline, {
// from: "0xCf1dE0b4d1e492080336909f70413a5F4E7eEc62",
// value: `${input * 10 ** 18}`,
// }, );
// }));
};
render() {
const { lastEditedField, inputCurrency, outputCurrency, input, output } = this.props;
const { exchangeRate } = this.state;
const inputLabel = this.getTokenLabel(inputCurrency);
const outputLabel = this.getTokenLabel(outputCurrency);
const estimatedText = '(estimated)'
const estimatedText = '(estimated)';
return (
<div className="swap">
@ -202,6 +251,7 @@ class Swap extends Component {
className={classnames('swap__cta-btn', {
'swap--inactive': !this.props.isConnected,
})}
onClick={this.onSwap}
>
Swap
</button>