Commit 78cdf11b authored by Noah Zinsmeister's avatar Noah Zinsmeister Committed by GitHub

Liquidity migration (#279)

* finalize initial migration

* remove redux

* fix token to token rate bug

* finalize initial migration

* fix test
parent eb394ee8
...@@ -47,7 +47,7 @@ ...@@ -47,7 +47,7 @@
"noZero": "Amount cannot be zero.", "noZero": "Amount cannot be zero.",
"mustBeETH": "One of the input must be ETH.", "mustBeETH": "One of the input must be ETH.",
"enterCurrencyOrLabelCont": "Enter a {{ inputCurrency }} or {{ label }} value to continue.", "enterCurrencyOrLabelCont": "Enter a {{ inputCurrency }} or {{ label }} value to continue.",
"youAreAdding": "You are adding between", "youAreAdding": "You are adding",
"and": "and", "and": "and",
"intoPool": "into the liquidity pool.", "intoPool": "into the liquidity pool.",
"outPool": "from the liquidity pool.", "outPool": "from the liquidity pool.",
......
...@@ -26,7 +26,7 @@ export default function CurrencyInputPanel({ ...@@ -26,7 +26,7 @@ export default function CurrencyInputPanel({
title, title,
description, description,
extraText, extraText,
extraTextClickHander, extraTextClickHander = () => {},
errorMessage, errorMessage,
disableUnlock, disableUnlock,
disableTokenSelect, disableTokenSelect,
......
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { withRouter, NavLink } from 'react-router-dom' import { withRouter, NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { dismissBetaMessage } from '../../ducks/app'
import { useBodyKeyDown } from '../../hooks' import { useBodyKeyDown } from '../../hooks'
import './navigation-tabs.scss' import './navigation-tabs.scss'
import { useApplicationContext } from '../../contexts/Application'
const tabOrder = [ const tabOrder = [
{ {
...@@ -26,9 +25,11 @@ const tabOrder = [ ...@@ -26,9 +25,11 @@ const tabOrder = [
} }
] ]
function NavigationTabs({ location: { pathname }, history, dismissBetaMessage, showBetaMessage }) { function NavigationTabs({ location: { pathname }, history }) {
const { t } = useTranslation() const { t } = useTranslation()
const { showBetaMessage, dismissBetaMessage } = useApplicationContext()
const navigate = useCallback( const navigate = useCallback(
direction => { direction => {
const tabIndex = tabOrder.findIndex(({ regex }) => pathname.match(regex)) const tabIndex = tabOrder.findIndex(({ regex }) => pathname.match(regex))
...@@ -73,13 +74,4 @@ function NavigationTabs({ location: { pathname }, history, dismissBetaMessage, s ...@@ -73,13 +74,4 @@ function NavigationTabs({ location: { pathname }, history, dismissBetaMessage, s
) )
} }
export default withRouter( export default withRouter(NavigationTabs)
connect(
state => ({
showBetaMessage: state.app.showBetaMessage
}),
dispatch => ({
dismissBetaMessage: () => dispatch(dismissBetaMessage())
})
)(NavigationTabs)
)
// string literals for actions
// set global web3 object
export const INITIALIZE_GLOBAL_WEB3 = 'INITIALIZE_GLOBAL_WEB3'
// web3 actions, all set from action creator to reducer to app
export const SET_WEB3_CONNECTION_STATUS = 'WEB3_CONNECTION_STATUS'
export const CHECK_WEB3_CONNECTION = 'CHECK_WEB3_CONNECTION'
export const SET_CURRENT_MASK_ADDRESS = 'SET_CURRENT_MASK_ADDRESS'
export const SET_INTERACTION_STATE = 'SET_INTERACTION_STATE'
export const SET_NETWORK_MESSAGE = 'SET_NETWORK_MESSAGE'
export const SET_BLOCK_TIMESTAMP = 'SET_BLOCK_TIMESTAMP'
export const SET_EXCHANGE_TYPE = 'SET_EXCHANGE_TYPE'
// actions to toggle divs
export const TOGGLE_ABOUT = 'TOGGLE_ABOUT'
export const TOGGLE_INVEST = 'TOGGLE_INVEST'
// CONTRACT actions in actions, action creator, reducer
export const FACTORY_CONTRACT_READY = 'FACTORY_CONTRACT_READY'
export const EXCHANGE_CONTRACT_READY = 'EXCHANGE_CONTRACT_READY'
export const TOKEN_CONTRACT_READY = 'TOKEN_CONTRACT_READY'
// actions for the exchange
export const SET_INPUT_BALANCE = 'SET_INPUT_BALANCE'
export const SET_OUTPUT_BALANCE = 'SET_OUTPUT_BALANCE'
export const SET_INPUT_TOKEN = 'SET_INPUT_TOKEN'
export const SET_OUTPUT_TOKEN = 'SET_OUTPUT_TOKEN'
export const SET_ETH_POOL_1 = 'SET_ETH_POOL_1'
export const SET_ETH_POOL_2 = 'SET_ETH_POOL_2'
export const SET_TOKEN_POOL_1 = 'SET_TOKEN_POOL_1'
export const SET_TOKEN_POOL_2 = 'SET_TOKEN_POOL_2'
export const SET_ALLOWANCE_APPROVAL_STATE = 'SET_ALLOWANCE_APPROVAL_STATE'
export const SET_EXCHANGE_INPUT_VALUE = 'SET_EXCHANGE_INPUT_VALUE'
export const SET_EXCHANGE_OUTPUT_VALUE = 'SET_EXCHANGE_OUTPUT_VALUE'
export const SET_EXCHANGE_RATE = 'SET_EXCHANGE_RATE'
export const SET_EXCHANGE_FEE = 'SET_EXCHANGE_FEE'
export const SET_INVEST_TOKEN = 'SET_INVEST_TOKEN'
export const SET_INVEST_ETH_POOL = 'SET_INVEST_ETH'
export const SET_INVEST_TOKEN_POOL = 'SET_INVEST_TOKENS'
export const SET_INVEST_TOKEN_ALLOWANCE = 'SET_INVEST_TOKEN_ALLOWANCE'
export const SET_INVEST_SHARES = 'SET_INVEST_SHARES'
export const SET_USER_SHARES = 'SET_USER_SHARES'
export const SET_INVEST_TOKEN_BALANCE = 'SET_INVEST_TOKEN_BALANCE'
export const SET_INVEST_ETH_BALANCE = 'SET_INVEST_ETH_BALANCE'
export const SET_INVEST_SHARES_INPUT = 'SET_INVEST_SHARES_INPUT'
export const SET_INVEST_ETH_REQUIRED = 'SET_INVEST_ETH_REQUIRED'
export const SET_INVEST_TOKENS_REQUIRED = 'SET_INVEST_TOKENS_REQUIRED'
export const SET_INVEST_CHECKED = 'SET_INVEST_CHECKED'
export const INSUFFICIENT_BALANCE = 'Insufficient balance'
...@@ -116,6 +116,12 @@ const TOKEN_DETAILS_MAINNET = { ...@@ -116,6 +116,12 @@ const TOKEN_DETAILS_MAINNET = {
[DECIMALS]: 18, [DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xF173214C720f58E03e194085B1DB28B50aCDeeaD' [EXCHANGE_ADDRESS]: '0xF173214C720f58E03e194085B1DB28B50aCDeeaD'
}, },
'0x6c6EE5e31d828De241282B9606C8e98Ea48526E2': {
[NAME]: 'HoloToken',
[SYMBOL]: 'HOT',
[DECIMALS]: 18,
[EXCHANGE_ADDRESS]: '0xd4777E164c6C683E10593E08760B803D58529a8E'
},
'0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0': { '0xA4e8C3Ec456107eA67d3075bF9e3DF3A75823DB0': {
[NAME]: 'LoomToken', [NAME]: 'LoomToken',
[SYMBOL]: 'LOOM', [SYMBOL]: 'LOOM',
......
This diff is collapsed.
const DISMISS_BETA_MESSAGE = 'app/app/dismissBetaMessage'
const initialState = {
showBetaMessage: true
}
export const dismissBetaMessage = () => ({ type: DISMISS_BETA_MESSAGE })
export default function appReducer(state = initialState, { type, payload }) {
switch (type) {
case DISMISS_BETA_MESSAGE:
return { ...state, showBetaMessage: false }
default:
return state
}
}
import { combineReducers } from 'redux'
import addresses from './addresses'
import app from './app'
import pending from './pending'
import web3connect from './web3connect'
export default combineReducers({
app,
addresses,
pending,
web3connect
})
const ADD_APPROVAL_TX = 'app/send/addApprovalTx'
const getInitialState = () => {
return {
approvals: {}
}
}
export const addApprovalTx = ({ tokenAddress, txId }) => ({
type: ADD_APPROVAL_TX,
payload: { tokenAddress, txId }
})
export default function sendReducer(state = getInitialState(), { type, payload }) {
switch (type) {
case ADD_APPROVAL_TX:
return {
approvals: {
...state.approvals,
[payload.tokenAddress]: payload.txId
}
}
default:
return state
}
}
This diff is collapsed.
export function retry(func, retryCount = 5) {
return new Promise((resolve, reject) => {
func().then(
(...args) => {
resolve(...args)
},
() => {
if (retryCount === 0) {
return reject()
}
setTimeout(() => retry(func, retryCount - 1).then(resolve, reject), 50)
}
)
})
}
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)
})
})
}
import promisify from './web3-promisfy'
export function getBlockDeadline(web3, deadline) {
return new Promise(async (resolve, reject) => {
const blockNumber = await promisify(web3, 'getBlockNumber')
if (!blockNumber && blockNumber !== 0) {
return reject()
}
const block = await promisify(web3, 'getBlock', blockNumber)
if (!block) {
return reject()
}
resolve(block.timestamp + deadline)
})
}
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import Web3Provider, { Connectors } from 'web3-react' import Web3Provider, { Connectors } from 'web3-react'
import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application' import ApplicationContextProvider, { Updater as ApplicationContextUpdater } from './contexts/Application'
...@@ -9,7 +8,6 @@ import StaticContextProvider, { Updater as StaticContextUpdater } from './contex ...@@ -9,7 +8,6 @@ import StaticContextProvider, { Updater as StaticContextUpdater } from './contex
import BlockContextProvider, { Updater as BlockContextUpdater } from './contexts/Block' import BlockContextProvider, { Updater as BlockContextUpdater } from './contexts/Block'
import App from './pages/App' import App from './pages/App'
import store from './store'
import './index.scss' import './index.scss'
import './i18n' import './i18n'
...@@ -52,13 +50,11 @@ function Updaters() { ...@@ -52,13 +50,11 @@ function Updaters() {
} }
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Web3Provider connectors={connectors} libraryName="ethers.js">
<Web3Provider connectors={connectors} libraryName="ethers.js"> <ContextProviders>
<ContextProviders> <Updaters />
<Updaters /> <App />
<App /> </ContextProviders>
</ContextProviders> </Web3Provider>,
</Web3Provider>
</Provider>,
document.getElementById('root') document.getElementById('root')
) )
import React, { useState, useEffect } from 'react' import React, { useEffect } from 'react'
import { connect } from 'react-redux'
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom' import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
import { useWeb3Context, Connectors } from 'web3-react' import { useWeb3Context, Connectors } from 'web3-react'
import NavigationTabs from '../components/NavigationTabs' import NavigationTabs from '../components/NavigationTabs'
import { updateNetwork, updateAccount, initialize, startWatching } from '../ducks/web3connect'
import { setAddresses } from '../ducks/addresses'
import Header from '../components/Header' import Header from '../components/Header'
import Swap from './Swap' import Swap from './Swap'
import Send from './Send' import Send from './Send'
...@@ -15,60 +12,34 @@ import './App.scss' ...@@ -15,60 +12,34 @@ import './App.scss'
const { Connector, InjectedConnector } = Connectors const { Connector, InjectedConnector } = Connectors
function App({ initialized, setAddresses, updateNetwork, updateAccount, initialize, startWatching }) { export default function App() {
const context = useWeb3Context() const { setConnector, setError, error, active, connectorName } = useWeb3Context()
// start web3-react on page-load // start web3-react on page-load
useEffect(() => { useEffect(() => {
context.setConnector('Injected', { suppressAndThrowErrors: true }).catch(error => { setConnector('Injected', { suppressAndThrowErrors: true }).catch(error => {
if (error.code === Connector.errorCodes.UNSUPPORTED_NETWORK) { if (error.code === Connector.errorCodes.UNSUPPORTED_NETWORK) {
context.setError(error, { connectorName: 'Injected' }) setError(error, { connectorName: 'Injected' })
} else { } else {
context.setConnector('Infura') setConnector('Infura')
} }
}) })
}, []) // eslint-disable-line react-hooks/exhaustive-deps }, []) // eslint-disable-line react-hooks/exhaustive-deps
// if the metamask user logs out, set the infura provider // if the metamask user logs out, set the infura provider
useEffect(() => { useEffect(() => {
if (context.error && context.error.code === InjectedConnector.errorCodes.UNLOCK_REQUIRED) { if (error && error.code === InjectedConnector.errorCodes.UNLOCK_REQUIRED) {
context.setConnector('Infura') setConnector('Infura')
} }
}, [context, context.error, context.connectorName]) }, [error, connectorName, setConnector])
// initialize redux network
const [reduxNetworkInitialized, setReduxNetworkInitialized] = useState(false)
useEffect(() => {
if (context.active) {
setAddresses(context.networkId)
updateNetwork(context.library._web3Provider, context.networkId)
setReduxNetworkInitialized(true)
}
}, [context.active, context.networkId]) // eslint-disable-line react-hooks/exhaustive-deps
// initialize redux account
const [reduxAccountInitialized, setReduxAccountInitialized] = useState(false)
useEffect(() => {
if (context.active) {
updateAccount(context.account)
setReduxAccountInitialized(true)
}
}, [context.active, context.account]) // eslint-disable-line react-hooks/exhaustive-deps
// initialize redux
useEffect(() => {
if (reduxNetworkInitialized && reduxAccountInitialized) {
initialize().then(startWatching)
}
}, [reduxNetworkInitialized, reduxAccountInitialized]) // eslint-disable-line react-hooks/exhaustive-deps
// active state // active state
if (initialized || context.error) { if (active || error) {
return ( return (
<div id="app-container"> <div id="app-container">
<Header /> <Header />
{/* this is an intermediate state before infura is set */} {/* this is an intermediate state before infura is set */}
{initialized && (!context.error || context.error.code === InjectedConnector.errorCodes.UNLOCK_REQUIRED) && ( {(!error || error.code === InjectedConnector.errorCodes.UNLOCK_REQUIRED) && (
<div className="app__wrapper"> <div className="app__wrapper">
<div className="body"> <div className="body">
<div className="body__content"> <div className="body__content">
...@@ -100,16 +71,3 @@ function App({ initialized, setAddresses, updateNetwork, updateAccount, initiali ...@@ -100,16 +71,3 @@ function App({ initialized, setAddresses, updateNetwork, updateAccount, initiali
// loading state // loading state
return null return null
} }
export default connect(
state => ({
initialized: state.web3connect.initialized
}),
dispatch => ({
setAddresses: networkId => dispatch(setAddresses(networkId)),
updateNetwork: (passedProvider, networkId) => dispatch(updateNetwork(passedProvider, networkId)),
updateAccount: account => dispatch(updateAccount(account)),
initialize: () => dispatch(initialize()),
startWatching: () => dispatch(startWatching())
})
)(App)
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import Web3Provider, { Connectors } from 'web3-react' import Web3Provider, { Connectors } from 'web3-react'
import App from './App' import App from './App'
import store from '../store'
// TODO, fix this hacky workaround // TODO, fix this hacky workaround
const { NetworkOnlyConnector } = Connectors const { NetworkOnlyConnector } = Connectors
...@@ -16,11 +14,9 @@ export const connectors = { Injected } ...@@ -16,11 +14,9 @@ export const connectors = { Injected }
it('renders without crashing', () => { it('renders without crashing', () => {
const div = document.createElement('div') const div = document.createElement('div')
ReactDOM.render( ReactDOM.render(
<Provider store={store}> <Web3Provider connectors={connectors} libraryName="ethers.js">
<Web3Provider connectors={connectors} libraryName="ethers.js"> <App />
<App /> </Web3Provider>,
</Web3Provider>
</Provider>,
div div
) )
ReactDOM.unmountComponentAtNode(div) ReactDOM.unmountComponentAtNode(div)
......
This diff is collapsed.
This diff is collapsed.
...@@ -69,6 +69,7 @@ ...@@ -69,6 +69,7 @@
} }
&__new-exchange-warning { &__new-exchange-warning {
margin-top: 1rem;
padding: 1rem; padding: 1rem;
margin-bottom: 2rem; margin-bottom: 2rem;
border: 1px solid rgba($pizazz-orange, 0.4); border: 1px solid rgba($pizazz-orange, 0.4);
......
...@@ -362,24 +362,36 @@ export default function Swap() { ...@@ -362,24 +362,36 @@ export default function Swap() {
if (amount && reserveETHFirst && reserveTokenFirst && reserveETHSecond && reserveTokenSecond) { if (amount && reserveETHFirst && reserveTokenFirst && reserveETHSecond && reserveTokenSecond) {
try { try {
const intermediateValue = if (independentField === INPUT) {
independentField === INPUT const intermediateValue = calculateEtherTokenOutputFromInput(amount, reserveTokenFirst, reserveETHFirst)
? calculateEtherTokenOutputFromInput(amount, reserveTokenFirst, reserveETHFirst) if (intermediateValue.lte(ethers.constants.Zero)) {
: calculateEtherTokenInputFromOutput(amount, reserveTokenFirst, reserveETHFirst) throw Error()
}
if (intermediateValue.lte(ethers.constants.Zero)) { const calculatedDependentValue = calculateEtherTokenOutputFromInput(
throw Error() intermediateValue,
} reserveETHSecond,
reserveTokenSecond
const calculatedDependentValue = )
independentField === INPUT if (calculatedDependentValue.lte(ethers.constants.Zero)) {
? calculateEtherTokenOutputFromInput(intermediateValue, reserveETHSecond, reserveTokenSecond) throw Error()
: calculateEtherTokenInputFromOutput(intermediateValue, reserveETHSecond, reserveTokenSecond) }
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
if (calculatedDependentValue.lte(ethers.constants.Zero)) { } else {
throw Error() const intermediateValue = calculateEtherTokenInputFromOutput(amount, reserveETHSecond, reserveTokenSecond)
if (intermediateValue.lte(ethers.constants.Zero)) {
throw Error()
}
// console.log('hi!', amountFormatter(intermediateValue, ))
const calculatedDependentValue = calculateEtherTokenInputFromOutput(
intermediateValue,
reserveTokenFirst,
reserveETHFirst
)
if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error()
}
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
} }
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
} catch { } catch {
setIndependentError(t('insufficientLiquidity')) setIndependentError(t('insufficientLiquidity'))
} }
......
...@@ -357,24 +357,36 @@ export default function Swap() { ...@@ -357,24 +357,36 @@ export default function Swap() {
if (amount && reserveETHFirst && reserveTokenFirst && reserveETHSecond && reserveTokenSecond) { if (amount && reserveETHFirst && reserveTokenFirst && reserveETHSecond && reserveTokenSecond) {
try { try {
const intermediateValue = if (independentField === INPUT) {
independentField === INPUT const intermediateValue = calculateEtherTokenOutputFromInput(amount, reserveTokenFirst, reserveETHFirst)
? calculateEtherTokenOutputFromInput(amount, reserveTokenFirst, reserveETHFirst) if (intermediateValue.lte(ethers.constants.Zero)) {
: calculateEtherTokenInputFromOutput(amount, reserveTokenFirst, reserveETHFirst) throw Error()
}
if (intermediateValue.lte(ethers.constants.Zero)) { const calculatedDependentValue = calculateEtherTokenOutputFromInput(
throw Error() intermediateValue,
} reserveETHSecond,
reserveTokenSecond
const calculatedDependentValue = )
independentField === INPUT if (calculatedDependentValue.lte(ethers.constants.Zero)) {
? calculateEtherTokenOutputFromInput(intermediateValue, reserveETHSecond, reserveTokenSecond) throw Error()
: calculateEtherTokenInputFromOutput(intermediateValue, reserveETHSecond, reserveTokenSecond) }
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
if (calculatedDependentValue.lte(ethers.constants.Zero)) { } else {
throw Error() const intermediateValue = calculateEtherTokenInputFromOutput(amount, reserveETHSecond, reserveTokenSecond)
if (intermediateValue.lte(ethers.constants.Zero)) {
throw Error()
}
// console.log('hi!', amountFormatter(intermediateValue, ))
const calculatedDependentValue = calculateEtherTokenInputFromOutput(
intermediateValue,
reserveTokenFirst,
reserveETHFirst
)
if (calculatedDependentValue.lte(ethers.constants.Zero)) {
throw Error()
}
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
} }
dispatchSwapState({ type: 'UPDATE_DEPENDENT', payload: calculatedDependentValue })
} catch { } catch {
setIndependentError(t('insufficientLiquidity')) setIndependentError(t('insufficientLiquidity'))
} }
......
import store from './store.dev'
export default store
import { applyMiddleware, compose, createStore } from 'redux'
import thunk from 'redux-thunk'
import initialState from './initial-state'
import reducer from '../ducks'
const middleware = [thunk]
const enhancers = []
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(reducer, initialState, composeEnhancers(applyMiddleware(...middleware), ...enhancers))
export default store
...@@ -44,12 +44,12 @@ export function getContract(address, ABI, library, account) { ...@@ -44,12 +44,12 @@ export function getContract(address, ABI, library, account) {
// account is optional // account is optional
export function getFactoryContract(networkId, library, account) { export function getFactoryContract(networkId, library, account) {
return getContract(FACTORY_ADDRESSES[networkId], FACTORY_ABI, getProviderOrSigner(library, account)) return getContract(FACTORY_ADDRESSES[networkId], FACTORY_ABI, library, account)
} }
// account is optional // account is optional
export function getExchangeContract(exchangeAddress, library, account) { export function getExchangeContract(exchangeAddress, library, account) {
return getContract(exchangeAddress, EXCHANGE_ABI, getProviderOrSigner(library, account)) return getContract(exchangeAddress, EXCHANGE_ABI, library, account)
} }
// get token name // get token name
......
This diff is collapsed.
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