diff --git a/web/.env b/web/.env index e88b1015..ef7dad9b 100644 --- a/web/.env +++ b/web/.env @@ -4,4 +4,9 @@ REACT_APP_NODE_URL=http://localhost:8545 REACT_APP_HYDRO_PROXY_ADDRESS=0x04f67E8b7C39A25e100847Cb167460D715215FEb REACT_APP_HYDRO_TOKEN_ADDRESS=0x4C4Fa7E8EA4cFCfC93DEAE2c0Cff142a1DD3a218 REACT_APP_WETH_TOKEN_ADDRESS=0x4a817489643A89a1428b2DD441c3fbe4DBf44789 -REACT_APP_NETWORK_ID=66 \ No newline at end of file +REACT_APP_NETWORK_ID=66 +REACT_APP_DAI_PRICE_IN_DOLLAR=1.00437 +REACT_APP_HOT_PRICE_IN_DOLLAR=0.00067 +REACT_APP_WETH_PRICE_IN_DOLLAR=168.21835 +REACT_APP_COIN_VASE_API_ADDRESS=https://rest.coinapi.io/v1 +REACT_APP_COIN_BASE_API_KEY=87822C5B-06F8-4231-B362-B4CA756B6CCC \ No newline at end of file diff --git a/web/src/App.js b/web/src/App.js index 508d7a76..de7649bb 100755 --- a/web/src/App.js +++ b/web/src/App.js @@ -1,6 +1,6 @@ import React from 'react'; import { connect } from 'react-redux'; -import { loadMarkets, loadTradeHistory } from './actions/markets'; +import { loadMarkets, loadExchange, loadTradeHistory } from './actions/markets'; import Header from './components/Header'; import WebsocketConnector from './components/WebsocketConnector'; import OrderBook from './components/Orderbook'; @@ -38,6 +38,7 @@ class App extends React.PureComponent { componentDidMount() { const { dispatch, currentMarket } = this.props; dispatch(loadMarkets()); + dispatch(loadExchange()); if (parseInt(env.NETWORK_ID) === 66) { this.initTestBrowserWallet(); } diff --git a/web/src/actions/markets.js b/web/src/actions/markets.js index ef534549..a322ec22 100644 --- a/web/src/actions/markets.js +++ b/web/src/actions/markets.js @@ -24,6 +24,24 @@ export const loadMarkets = () => { }; }; +export const loadExchange = () => { + return async (dispatch, getState) => { + try { + const res = await Promise.all([api.coinBaseGet('/exchangerate/DAI/USD'), api.coinBaseGet('/exchangerate/HOT/USD'), api.coinBaseGet('/exchangerate/ETH/USD')]); + if (res) { + const dollarExchange = {DAI: res[0]['data']['rate'], HOT: res[1]['data']['rate'], WETH: res[2]['data']['rate']} + return dispatch({ + type: 'LOAD_DOLLAR_EXCHANGE_RATE', + payload: dollarExchange + }); + } + } catch (error) { + console.log(error); + return; + } + } +} + // load current market trade history export const loadTradeHistory = marketID => { return async (dispatch, getState) => { diff --git a/web/src/components/Orderbook/index.js b/web/src/components/Orderbook/index.js index c06d9c35..bfd3c4d5 100644 --- a/web/src/components/Orderbook/index.js +++ b/web/src/components/Orderbook/index.js @@ -37,28 +37,52 @@ class OrderBook extends React.Component { this.lastUpdatedAt = new Date(); } + maxAmount(asks) { + let max = 0; + asks.forEach(element => { + const amount = element[1].toFixed(this.props.currentMarket.amountDecimals); + if (max < amount) { + max = amount; + } + }); + return max; + } + + calculateBarWidth(maxAmount, Amount) { + const width = ~~((Amount / maxAmount) * 20); //20 is the max percentage + return width + '%'; + } + render() { - let { bids, asks, websocketConnected, currentMarket } = this.props; + const { bids, asks, websocketConnected, currentMarket, dollarExchangeRate } = this.props; + const asksArray = asks.slice(-20).reverse().toArray(); + const bidsArray = bids.slice(-20).reverse().toArray(); + const asksMaxAmount = this.maxAmount(asksArray); + const bidsMaxAmount = this.maxAmount(bidsArray); return ( -
+
-
Amount
-
Price
+
Amount
+
Price
+
Amount
-
- {asks - .slice(-20) - .reverse() - .toArray() - .map(([price, amount]) => { +
+ {asksArray + .map(([price, amount], index) => { + const dollarValue = price * dollarExchangeRate; return ( -
-
- {amount.toFixed(currentMarket.amountDecimals)} +
+
+

{amount.toFixed(currentMarket.amountDecimals)}

+
+
+
+
{price.toFixed(currentMarket.priceDecimals)}
+
{dollarValue.toFixed(2)} USD
-
{price.toFixed(currentMarket.priceDecimals)}
+
-
); })} @@ -74,17 +98,21 @@ class OrderBook extends React.Component {
)}
-
- {bids - .slice(0, 20) - .toArray() - .map(([price, amount]) => { +
+ {bidsArray + .map(([price, amount], index) => { + const dollarValue = price * dollarExchangeRate; return ( -
-
- {amount.toFixed(currentMarket.amountDecimals)} +
+
-
+
+
{price.toFixed(currentMarket.priceDecimals)}
+
{dollarValue.toFixed(2)} USD
+
+
+
+

{amount.toFixed(currentMarket.amountDecimals)}

-
{price.toFixed(currentMarket.priceDecimals)}
); })} @@ -96,13 +124,15 @@ class OrderBook extends React.Component { } const mapStateToProps = state => { + const currentMarket = state.market.getIn(['markets', 'currentMarket']); return { asks: state.market.getIn(['orderbook', 'asks']), bids: state.market.getIn(['orderbook', 'bids']), loading: false, - currentMarket: state.market.getIn(['markets', 'currentMarket']), + currentMarket, websocketConnected: state.config.get('websocketConnected'), - theme: state.config.get('theme') + theme: state.config.get('theme'), + dollarExchangeRate: state.market.getIn(['exchangeRate', 'data', currentMarket['quoteToken']]), }; }; diff --git a/web/src/components/Orderbook/styles.scss b/web/src/components/Orderbook/styles.scss index ee8b372c..67c42119 100644 --- a/web/src/components/Orderbook/styles.scss +++ b/web/src/components/Orderbook/styles.scss @@ -1,4 +1,7 @@ +@import '../../styles/variables.scss'; .orderbook { + height: 508px; + overflow-y: scroll; .header, .status { height: 40px; @@ -10,6 +13,40 @@ } .ask, .bid { - min-height: 24px; + min-height: 30px; + } + .amount { + display: inline; + position: relative; + z-index: 1; + } + .price-container-ask { + background-color: $grey-95; + } + .bid-price-container { + background-color: $pinkLight; + } + .price { + font-size: 15px; + } + .currency { + color: $black; + font-size: 9px; + } + .amount-bar-ask { + background-color: $light; + background-position: 50% center; + height: 22px; + position: absolute; + right: 1px; + top: -1px; + } + .amount-bar-bid { + background-color: $pinkLight; + background-position: 50% center; + height: 22px; + position: absolute; + left: 1px; + top: -1px; } } diff --git a/web/src/lib/api.js b/web/src/lib/api.js index 0e39c6b2..e1f84fcf 100644 --- a/web/src/lib/api.js +++ b/web/src/lib/api.js @@ -39,13 +39,25 @@ const _request = (method, url, ...args) => { return getAxiosInstance()[method](`${env.API_ADDRESS}${url}`, ...args); }; +const _coinBaseRequest = (method, url, ...args) => { + const instance = axios.create({ + baseURL: env.COIN_BASE_API_ADDRESS, + headers: { + 'X-CoinAPI-Key': env.COIN_BASE_API_KEY + } + }); + + return instance[method](url, ...args); +} + const api = { get: (url, ...args) => _request('get', url, ...args), delete: (url, ...args) => _request('delete', url, ...args), head: (url, ...args) => _request('head', url, ...args), post: (url, ...args) => _request('post', url, ...args), put: (url, ...args) => _request('put', url, ...args), - patch: (url, ...args) => _request('patch', url, ...args) + patch: (url, ...args) => _request('patch', url, ...args), + coinBaseGet: (url, ...args) => _coinBaseRequest('get', url, ...args) }; export default api; diff --git a/web/src/lib/env.js b/web/src/lib/env.js index 171eda42..a83a1725 100644 --- a/web/src/lib/env.js +++ b/web/src/lib/env.js @@ -11,5 +11,10 @@ export default { HYDRO_PROXY_ADDRESS: _env.REACT_APP_HYDRO_PROXY_ADDRESS, HYDRO_TOKEN_ADDRESS: _env.REACT_APP_HYDRO_TOKEN_ADDRESS, WETH_TOKEN_ADDRESS: _env.REACT_APP_WETH_TOKEN_ADDRESS, - NETWORK_ID: _env.REACT_APP_NETWORK_ID + NETWORK_ID: _env.REACT_APP_NETWORK_ID, + DAI_PRICE_IN_DOLLAR: _env.REACT_APP_DAI_PRICE_IN_DOLLAR, + HOT_PRICE_IN_DOLLAR: _env.REACT_APP_HOT_PRICE_IN_DOLLAR, + WETH_PRICE_IN_DOLLAR: _env.REACT_APP_WETH_PRICE_IN_DOLLAR, + COIN_BASE_API_ADDRESS: _env.REACT_APP_COIN_VASE_API_ADDRESS, + COIN_BASE_API_KEY: _env.REACT_APP_COIN_BASE_API_KEY, }; diff --git a/web/src/reducers/market.js b/web/src/reducers/market.js index 04d7892d..b928c8d7 100644 --- a/web/src/reducers/market.js +++ b/web/src/reducers/market.js @@ -1,4 +1,5 @@ import { Map, List, OrderedMap } from 'immutable'; +import env from '../lib/env'; const initialOrderbook = Map({ bids: List(), @@ -34,7 +35,16 @@ const initialState = Map({ tokenPrices: Map({ loading: true, data: {} - }) + }), + + exchangeRate: Map({ + loading: true, + data: Map({ + DAI: env.DAI_PRICE_IN_DOLLAR, + HOT: env.HOT_PRICE_IN_DOLLAR, + WETH: env.WETH_PRICE_IN_DOLLAR, + }) + }), }); const reverseBigNumberComparator = (a, b) => { @@ -96,6 +106,11 @@ export default (state = initialState, action) => { state = state.setIn(['orderbook', side], state.getIn(['orderbook', side]).sort(reverseBigNumberComparator)); return state; + + case 'LOAD_DOLLAR_EXCHANGE_RATE': + state = state.updateIn(['exchangeRate', 'loading'], () => false); + state = state.updateIn(['exchangeRate', 'data'], () => Map(action.payload)); + return state; default: return state; } diff --git a/web/src/styles/variables.scss b/web/src/styles/variables.scss index 369b39d1..f81839b2 100644 --- a/web/src/styles/variables.scss +++ b/web/src/styles/variables.scss @@ -4,6 +4,9 @@ $blueHover: #5c6fac; $red: #ff6f75; $green: #00d99f; $grey: #9b9b9b; +$grey-95: #f2f2f2; $borderGrey: #e0e0e0; $black: #505d6f; $white: #fff; +$light: #cce3f1; +$pinkLight: #ffe6ee; \ No newline at end of file