Merge pull request #103 from gnosis/99-app-state
#99 Refactor redux store
This commit is contained in:
commit
a689c3b44d
32
package.json
32
package.json
|
@ -80,9 +80,9 @@
|
|||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-final-form": "^4.1.0",
|
||||
"react-hot-loader": "^4.8.2",
|
||||
"react-hot-loader": "4.8.4",
|
||||
"react-infinite-scroll-component": "^4.5.2",
|
||||
"react-redux": "^6.0.1",
|
||||
"react-redux": "7.0.2",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"recompose": "^0.30.0",
|
||||
"redux": "^4.0.1",
|
||||
|
@ -117,11 +117,11 @@
|
|||
"@babel/preset-flow": "^7.0.0-beta.40",
|
||||
"@babel/preset-react": "^7.0.0-beta.40",
|
||||
"@sambego/storybook-state": "^1.0.7",
|
||||
"@storybook/addon-actions": "^5.0.6",
|
||||
"@storybook/addon-knobs": "^5.0.6",
|
||||
"@storybook/addon-links": "^5.0.6",
|
||||
"@storybook/react": "^5.0.6",
|
||||
"autoprefixer": "^9.4.10",
|
||||
"@storybook/addon-actions": "5.0.9",
|
||||
"@storybook/addon-knobs": "5.0.9",
|
||||
"@storybook/addon-links": "5.0.9",
|
||||
"@storybook/react": "5.0.9",
|
||||
"autoprefixer": "9.5.1",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^24.1.0",
|
||||
|
@ -134,38 +134,38 @@
|
|||
"detect-port": "^1.2.2",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb": "^17.1.0",
|
||||
"eslint-plugin-flowtype": "^3.4.2",
|
||||
"eslint-plugin-import": "^2.9.0",
|
||||
"eslint-plugin-flowtype": "3.6.1",
|
||||
"eslint-plugin-import": "2.17.2",
|
||||
"eslint-plugin-jest": "^22.3.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||
"eslint-plugin-react": "^7.7.0",
|
||||
"ethereumjs-abi": "^0.6.7",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^3.0.1",
|
||||
"flow-bin": "0.96.0",
|
||||
"flow-bin": "0.97.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"html-loader": "^0.5.5",
|
||||
"html-webpack-plugin": "^3.0.4",
|
||||
"jest": "^24.1.0",
|
||||
"json-loader": "^0.5.7",
|
||||
"mini-css-extract-plugin": "^0.5.0",
|
||||
"mini-css-extract-plugin": "0.6.0",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-mixins": "^6.2.0",
|
||||
"postcss-simple-vars": "^5.0.2",
|
||||
"pre-commit": "^1.2.2",
|
||||
"prettier-eslint-cli": "^4.7.1",
|
||||
"run-with-testrpc": "^0.3.0",
|
||||
"run-with-testrpc": "0.3.1",
|
||||
"storybook-host": "^5.0.3",
|
||||
"storybook-router": "^0.3.3",
|
||||
"style-loader": "^0.23.1",
|
||||
"truffle": "^5.0.10",
|
||||
"truffle": "5.0.12",
|
||||
"truffle-contract": "^4.0.11",
|
||||
"truffle-solidity-loader": "^0.1.10",
|
||||
"truffle-solidity-loader": "0.1.12",
|
||||
"uglifyjs-webpack-plugin": "^2.1.2",
|
||||
"webpack": "^4.1.1",
|
||||
"webpack-bundle-analyzer": "^3.1.0",
|
||||
"webpack-bundle-analyzer": "3.3.2",
|
||||
"webpack-cli": "^3.2.3",
|
||||
"webpack-dev-server": "^3.1.0",
|
||||
"webpack-dev-server": "3.3.1",
|
||||
"webpack-manifest-plugin": "^2.0.0-rc.2"
|
||||
}
|
||||
}
|
||||
|
|
14
readme.md
14
readme.md
|
@ -11,12 +11,14 @@ These instructions will get you a copy of the project up and running on your loc
|
|||
What things you need to install the software and how to install them
|
||||
|
||||
```
|
||||
npm install truffle // recommended usage of -g flag
|
||||
npm install ganache-cli // recommended usage of -g flag
|
||||
npm install flow-type // recommended usage of -g flag
|
||||
yarn add truffle // recommended usage of -g flag
|
||||
yarn add ganache-cli // recommended usage of -g flag
|
||||
yarn add flow-type // recommended usage of -g flag
|
||||
git clone https://github.com/gnosis/safe-contracts.git
|
||||
```
|
||||
|
||||
We use [yarn](https://yarnpkg.com) in our infrastacture, so we decided to go with yarn in the README
|
||||
|
||||
### Installing
|
||||
|
||||
A step by step series of examples that tell you have to get a development env running
|
||||
|
@ -29,14 +31,14 @@ ganache-cli -b 3
|
|||
Start the project in the other one
|
||||
```
|
||||
cd safe-contracts && truffle compile && truffle migrate && cd ..
|
||||
npm install
|
||||
npm start
|
||||
yarn install
|
||||
yarn start
|
||||
```
|
||||
|
||||
## Running the tests
|
||||
|
||||
```
|
||||
npm test
|
||||
yarn test
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,9 @@ import ReactDOM from 'react-dom'
|
|||
import Root from '~/components/Root'
|
||||
import { store } from '~/store'
|
||||
import loadSafesFromStorage from '~/routes/safe/store/actions/loadSafesFromStorage'
|
||||
import loadActiveTokens from '~/logic/tokens/store/actions/loadActiveTokens'
|
||||
|
||||
store.dispatch(loadActiveTokens())
|
||||
store.dispatch(loadSafesFromStorage())
|
||||
|
||||
ReactDOM.render(<Root />, document.getElementById('root'))
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import { executeTransaction, approveTransaction } from '~/logic/safe/safeBlockchainOperations'
|
||||
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { getGnosisSafeContract } from '~/logic/contracts/safeContracts'
|
||||
import { storeSubject } from '~/utils/storage/transactions'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { type Owner } from '~/routes/safe/store/models/owner'
|
||||
import { List, Map } from 'immutable'
|
||||
import { loadFromStorage, saveToStorage } from '~/utils/storage'
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// @flow
|
||||
import { createAction } from 'redux-actions'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { setActiveTokens, getActiveTokens, setToken } from '~/logic/tokens/utils/tokensStorage'
|
||||
import { saveActiveTokens, getActiveTokens, setToken } from '~/logic/tokens/utils/tokensStorage'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { type GlobalState } from '~/store/'
|
||||
|
||||
export const ADD_TOKEN = 'ADD_TOKEN'
|
||||
|
||||
|
@ -14,17 +14,16 @@ type AddTokenProps = {
|
|||
|
||||
export const addToken = createAction<string, *, *>(
|
||||
ADD_TOKEN,
|
||||
(safeAddress: string, token: Token): AddTokenProps => ({
|
||||
safeAddress,
|
||||
(token: Token): AddTokenProps => ({
|
||||
token,
|
||||
}),
|
||||
)
|
||||
|
||||
const saveToken = (safeAddress: string, token: Token) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
dispatch(addToken(safeAddress, token))
|
||||
dispatch(addToken(token))
|
||||
|
||||
const activeTokens = await getActiveTokens(safeAddress)
|
||||
await setActiveTokens(safeAddress, activeTokens.push(token.toJS()))
|
||||
await saveActiveTokens(safeAddress, activeTokens.push(token.toJS()))
|
||||
setToken(safeAddress, token)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
// @flow
|
||||
import { createAction } from 'redux-actions'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { removeFromActiveTokens } from '~/logic/tokens/utils/tokensStorage'
|
||||
|
||||
export const DISABLE_TOKEN = 'DISABLE_TOKEN'
|
||||
|
||||
export const disableToken = createAction<string, *, *>(DISABLE_TOKEN, (safeAddress: string, token: Token) => ({
|
||||
safeAddress,
|
||||
token,
|
||||
}))
|
||||
|
||||
const hideToken = (safeAddress: string, token: Token) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
dispatch(disableToken(safeAddress, token))
|
||||
|
||||
await removeFromActiveTokens(safeAddress, token)
|
||||
}
|
||||
|
||||
export default hideToken
|
|
@ -1,22 +0,0 @@
|
|||
// @flow
|
||||
import { createAction } from 'redux-actions'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { setActiveTokens, getActiveTokens } from '~/logic/tokens/utils/tokensStorage'
|
||||
|
||||
export const ENABLE_TOKEN = 'ENABLE_TOKEN'
|
||||
|
||||
export const enableToken = createAction<string, *, *>(ENABLE_TOKEN, (safeAddress: string, token: Token) => ({
|
||||
safeAddress,
|
||||
token,
|
||||
}))
|
||||
|
||||
const setTokenEnabled = (safeAddress: string, token: Token) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
dispatch(enableToken(safeAddress, token))
|
||||
|
||||
const activeTokens = await getActiveTokens(safeAddress)
|
||||
await setActiveTokens(safeAddress, activeTokens.push(token))
|
||||
}
|
||||
|
||||
export default setTokenEnabled
|
|
@ -1,17 +1,14 @@
|
|||
// @flow
|
||||
import { List, Map } from 'immutable'
|
||||
import { List } from 'immutable'
|
||||
import contract from 'truffle-contract'
|
||||
import axios from 'axios'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import StandardToken from '@gnosis.pm/util-contracts/build/contracts/GnosisStandardToken.json'
|
||||
import HumanFriendlyToken from '@gnosis.pm/util-contracts/build/contracts/HumanFriendlyToken.json'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { makeToken, type Token, type TokenProps } from '~/logic/tokens/store/model/token'
|
||||
import { makeToken, type TokenProps } from '~/logic/tokens/store/model/token'
|
||||
import { ensureOnce } from '~/utils/singleton'
|
||||
import { getActiveTokens, getTokens } from '~/logic/tokens/utils/tokensStorage'
|
||||
import { getSafeEthToken } from '~/logic/tokens/utils/tokenHelpers'
|
||||
import saveTokens from './saveTokens'
|
||||
import { getRelayUrl } from '~/config/index'
|
||||
|
||||
|
@ -34,66 +31,28 @@ export const getHumanFriendlyToken = ensureOnce(createHumanFriendlyTokenContract
|
|||
|
||||
export const getStandardTokenContract = ensureOnce(createStandardTokenContract)
|
||||
|
||||
export const calculateBalanceOf = async (tokenAddress: string, address: string, decimals: number) => {
|
||||
const erc20Token = await getStandardTokenContract()
|
||||
let balance = 0
|
||||
|
||||
try {
|
||||
const token = await erc20Token.at(tokenAddress)
|
||||
balance = await token.balanceOf(address)
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch token balances: ', err)
|
||||
}
|
||||
|
||||
return new BigNumber(balance).div(10 ** decimals).toString()
|
||||
}
|
||||
|
||||
export const fetchTokensData = async () => {
|
||||
const fetchTokenList = async () => {
|
||||
const apiUrl = getRelayUrl()
|
||||
const url = `${apiUrl}/tokens`
|
||||
const errMsg = 'Error querying safe balances'
|
||||
return axios.get(url, errMsg)
|
||||
}
|
||||
|
||||
export const fetchTokens = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const tokens: List<TokenProps> = await getActiveTokens(safeAddress)
|
||||
const ethBalance = await getSafeEthToken(safeAddress)
|
||||
const customTokens = await getTokens(safeAddress)
|
||||
const {
|
||||
data: { results },
|
||||
} = await fetchTokensData()
|
||||
|
||||
export const fetchTokens = () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
try {
|
||||
const balancesRecords = await Promise.all(
|
||||
results.map(async (item: TokenProps) => {
|
||||
const status = tokens.findIndex(activeToken => activeToken.name === item.name) !== -1
|
||||
const funds = status ? await calculateBalanceOf(item.address, safeAddress, item.decimals) : '0'
|
||||
const {
|
||||
data: { results: tokenList },
|
||||
} = await fetchTokenList()
|
||||
|
||||
return makeToken({ ...item, status, funds })
|
||||
}),
|
||||
)
|
||||
const tokens = List(tokenList.map((token: TokenProps) => makeToken(token)))
|
||||
|
||||
const customTokenRecords = await Promise.all(
|
||||
customTokens.map(async (item: TokenProps) => {
|
||||
const status = tokens.findIndex(activeToken => activeToken.name === item.name) !== -1
|
||||
const funds = status ? await calculateBalanceOf(item.address, safeAddress, item.decimals) : '0'
|
||||
|
||||
return makeToken({ ...item, status, funds })
|
||||
}),
|
||||
)
|
||||
|
||||
const balances: Map<string, Token> = Map().withMutations((map) => {
|
||||
balancesRecords.forEach(record => map.set(record.address, record))
|
||||
customTokenRecords.forEach(record => map.set(record.address, record))
|
||||
|
||||
map.set(ethBalance.address, ethBalance)
|
||||
})
|
||||
|
||||
return dispatch(saveTokens(safeAddress, balances))
|
||||
dispatch(saveTokens(tokens))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log('Error fetching tokens... ' + err)
|
||||
console.log('Error fetching token list ' + err)
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
export default fetchTokens
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// @flow
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { Map, List } from 'immutable'
|
||||
import { type TokenProps, type Token, makeToken } from '~/logic/tokens/store/model/token'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { getActiveTokens } from '~/logic/tokens/utils/tokensStorage'
|
||||
import saveTokens from './saveTokens'
|
||||
|
||||
const loadActiveTokens = () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
try {
|
||||
const tokens: Map<string, TokenProps> = await getActiveTokens()
|
||||
|
||||
const tokenRecordsList: List<Token> = List(
|
||||
Object.values(tokens).map(token => makeToken(token)),
|
||||
)
|
||||
|
||||
dispatch(saveTokens(tokenRecordsList))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error while loading active tokens from storage:', err)
|
||||
}
|
||||
}
|
||||
|
||||
export default loadActiveTokens
|
|
@ -1,37 +1,19 @@
|
|||
// @flow
|
||||
import { Map, List } from 'immutable'
|
||||
import { Map } from 'immutable'
|
||||
import { createAction } from 'redux-actions'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { ensureOnceAsync } from '~/utils/singleton'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { setActiveTokens } from '~/logic/tokens/utils/tokensStorage'
|
||||
import { calculateActiveErc20TokensFrom } from '~/logic/tokens/utils/tokenHelpers'
|
||||
|
||||
export const ADD_TOKENS = 'ADD_TOKENS'
|
||||
|
||||
const setTokensOnce = ensureOnceAsync(setActiveTokens)
|
||||
|
||||
type TokenProps = {
|
||||
safeAddress: string,
|
||||
tokens: Map<string, Token>,
|
||||
}
|
||||
|
||||
export const addTokens = createAction<string, *, *>(
|
||||
const addTokens = createAction<string, *, *>(
|
||||
ADD_TOKENS,
|
||||
(safeAddress: string, tokens: Map<string, Token>): TokenProps => ({
|
||||
safeAddress,
|
||||
(tokens: Map<string, Token>): TokenProps => ({
|
||||
tokens,
|
||||
}),
|
||||
)
|
||||
|
||||
const saveTokens = (safeAddress: string, tokens: Map<string, Token>) => async (
|
||||
dispatch: ReduxDispatch<GlobalState>,
|
||||
) => {
|
||||
dispatch(addTokens(safeAddress, tokens))
|
||||
|
||||
const activeAddresses: List<Token> = calculateActiveErc20TokensFrom(tokens.toList())
|
||||
await setTokensOnce(safeAddress, activeAddresses)
|
||||
}
|
||||
|
||||
export default saveTokens
|
||||
export default addTokens
|
||||
|
|
|
@ -8,9 +8,7 @@ export type TokenProps = {
|
|||
symbol: string,
|
||||
decimals: number,
|
||||
logoUri: string,
|
||||
funds: string,
|
||||
status: boolean,
|
||||
removable: boolean,
|
||||
balance: string,
|
||||
}
|
||||
|
||||
export const makeToken: RecordFactory<TokenProps> = Record({
|
||||
|
@ -19,9 +17,9 @@ export const makeToken: RecordFactory<TokenProps> = Record({
|
|||
symbol: '',
|
||||
decimals: 0,
|
||||
logoUri: '',
|
||||
funds: '0',
|
||||
status: true,
|
||||
removable: false,
|
||||
balance: undefined,
|
||||
})
|
||||
|
||||
// balance is only set in extendedSafeTokensSelector when we display user's token balances
|
||||
|
||||
export type Token = RecordOf<TokenProps>
|
||||
|
|
|
@ -5,8 +5,6 @@ import { type Token } from '~/logic/tokens/store/model/token'
|
|||
import { ADD_TOKEN } from '~/logic/tokens/store/actions/addToken'
|
||||
import { REMOVE_TOKEN } from '~/logic/tokens/store/actions/removeToken'
|
||||
import { ADD_TOKENS } from '~/logic/tokens/store/actions/saveTokens'
|
||||
import { DISABLE_TOKEN } from '~/logic/tokens/store/actions/disableToken'
|
||||
import { ENABLE_TOKEN } from '~/logic/tokens/store/actions/enableToken'
|
||||
|
||||
export const TOKEN_REDUCER_ID = 'tokens'
|
||||
|
||||
|
@ -15,39 +13,27 @@ export type State = Map<string, Map<string, Token>>
|
|||
export default handleActions<State, *>(
|
||||
{
|
||||
[ADD_TOKENS]: (state: State, action: ActionType<Function>): State => {
|
||||
const { safeAddress, tokens } = action.payload
|
||||
const { tokens } = action.payload
|
||||
|
||||
return state.update(safeAddress, (prevSafe: Map<string, Token>) => {
|
||||
if (!prevSafe) {
|
||||
return tokens
|
||||
}
|
||||
|
||||
return prevSafe.equals(tokens) ? prevSafe : tokens
|
||||
const newState = state.withMutations((map) => {
|
||||
tokens.forEach((token) => {
|
||||
map.set(token.address, token)
|
||||
})
|
||||
})
|
||||
|
||||
return newState
|
||||
},
|
||||
[ADD_TOKEN]: (state: State, action: ActionType<Function>): State => {
|
||||
const { safeAddress, token } = action.payload
|
||||
const { token } = action.payload
|
||||
const { address: tokenAddress } = token
|
||||
|
||||
return state.setIn([safeAddress, tokenAddress], token)
|
||||
return state.set(tokenAddress, token)
|
||||
},
|
||||
[REMOVE_TOKEN]: (state: State, action: ActionType<Function>): State => {
|
||||
const { safeAddress, token } = action.payload
|
||||
const { token } = action.payload
|
||||
const { address: tokenAddress } = token
|
||||
|
||||
return state.removeIn([safeAddress, tokenAddress])
|
||||
},
|
||||
[DISABLE_TOKEN]: (state: State, action: ActionType<Function>): State => {
|
||||
const { safeAddress, token } = action.payload
|
||||
const { address: tokenAddress } = token
|
||||
|
||||
return state.setIn([safeAddress, tokenAddress, 'status'], false)
|
||||
},
|
||||
[ENABLE_TOKEN]: (state: State, action: ActionType<Function>): State => {
|
||||
const { safeAddress, token } = action.payload
|
||||
const { address: tokenAddress } = token
|
||||
|
||||
return state.setIn([safeAddress, tokenAddress, 'status'], true)
|
||||
return state.remove(tokenAddress)
|
||||
},
|
||||
},
|
||||
Map(),
|
||||
|
|
|
@ -1,45 +1,19 @@
|
|||
// @flow
|
||||
import { List, Map } from 'immutable'
|
||||
import { createSelector, type Selector } from 'reselect'
|
||||
import { safeParamAddressSelector, type RouterProps } from '~/routes/safe/store/selectors'
|
||||
import { type RouterProps } from '~/routes/safe/store/selectors'
|
||||
import { type GlobalState } from '~/store'
|
||||
import { TOKEN_REDUCER_ID } from '~/logic/tokens/store/reducer/tokens'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
|
||||
const balancesSelector = (state: GlobalState) => state[TOKEN_REDUCER_ID]
|
||||
export const tokensSelector = (state: GlobalState) => state[TOKEN_REDUCER_ID]
|
||||
|
||||
export const tokensSelector: Selector<GlobalState, RouterProps, Map<string, Token>> = createSelector(
|
||||
balancesSelector,
|
||||
safeParamAddressSelector,
|
||||
(tokens: Map<string, Map<string, Token>>, address: string) => {
|
||||
if (!address) {
|
||||
return Map()
|
||||
}
|
||||
|
||||
return tokens.get(address) || Map()
|
||||
},
|
||||
)
|
||||
|
||||
export const tokenListSelector = createSelector(
|
||||
export const tokenListSelector: Selector<GlobalState, Map<string, Token>, List<Token>> = createSelector(
|
||||
tokensSelector,
|
||||
(tokens: Map<string, Token>) => tokens.toList(),
|
||||
)
|
||||
|
||||
export const activeTokensSelector = createSelector(
|
||||
tokenListSelector,
|
||||
(tokens: List<Token>) => tokens.filter((token: Token) => token.get('status')),
|
||||
)
|
||||
|
||||
export const orderedTokenListSelector = createSelector(
|
||||
export const orderedTokenListSelector: Selector<GlobalState, RouterProps, List<Token>> = createSelector(
|
||||
tokenListSelector,
|
||||
(tokens: List<Token>) => tokens.sortBy((token: Token) => token.get('symbol')),
|
||||
)
|
||||
|
||||
export const tokenAddressesSelector = createSelector(
|
||||
tokenListSelector,
|
||||
(balances: List<Token>) => {
|
||||
const addresses = List().withMutations(list => balances.map(token => list.push(token.address)))
|
||||
|
||||
return addresses
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import logo from '~/assets/icons/icon_etherTokens.svg'
|
||||
import { getBalanceInEtherOf } from '~/logic/wallets/getWeb3'
|
||||
import { makeToken, type Token } from '~/logic/tokens/store/model/token'
|
||||
|
||||
export const ETH_ADDRESS = '0'
|
||||
export const ETH_ADDRESS = '0x000'
|
||||
export const isEther = (symbol: string) => symbol === 'ETH'
|
||||
|
||||
export const getSafeEthToken = async (safeAddress: string) => {
|
||||
const balance = await getBalanceInEtherOf(safeAddress)
|
||||
|
||||
const ethBalance = makeToken({
|
||||
address: '0',
|
||||
export const getEthAsToken = (balance: string) => {
|
||||
const eth = makeToken({
|
||||
address: ETH_ADDRESS,
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
logoUri: logo,
|
||||
funds: balance,
|
||||
balance,
|
||||
})
|
||||
|
||||
return ethBalance
|
||||
return eth
|
||||
}
|
||||
|
||||
export const calculateActiveErc20TokensFrom = (tokens: List<Token>) => {
|
||||
const addresses = List().withMutations(list => tokens.forEach((token: Token) => {
|
||||
const activeTokens = List().withMutations(list => tokens.forEach((token: Token) => {
|
||||
const isDeactivated = isEther(token.symbol) || !token.status
|
||||
if (isDeactivated) {
|
||||
return
|
||||
|
@ -32,5 +29,5 @@ export const calculateActiveErc20TokensFrom = (tokens: List<Token>) => {
|
|||
list.push(token)
|
||||
}))
|
||||
|
||||
return addresses
|
||||
return activeTokens
|
||||
}
|
||||
|
|
|
@ -1,44 +1,41 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { List, Map } from 'immutable'
|
||||
import { type Token, type TokenProps } from '~/logic/tokens/store/model/token'
|
||||
import { loadFromStorage, saveToStorage } from '~/utils/storage'
|
||||
|
||||
export const ACTIVE_TOKENS_KEY = 'ACTIVE_TOKENS'
|
||||
export const TOKENS_KEY = 'TOKENS'
|
||||
export const CUSTOM_TOKENS_KEY = 'CUSTOM_TOKENS'
|
||||
|
||||
const getActiveTokensKey = (safeAddress: string) => `${ACTIVE_TOKENS_KEY}-${safeAddress}`
|
||||
const getTokensKey = (safeAddress: string) => `${TOKENS_KEY}-${safeAddress}`
|
||||
// Tokens which are active at least in one of used safes in the app should be saved to localstorage
|
||||
// to avoid iterating a large amount of data of tokens from the backend
|
||||
// Custom tokens should be saved too unless they're deleted (marking them as inactive doesn't count)
|
||||
|
||||
export const setActiveTokens = async (safeAddress: string, tokens: List<TokenProps>) => {
|
||||
export const saveActiveTokens = async (tokens: Map<string, Token>) => {
|
||||
try {
|
||||
const key = getActiveTokensKey(safeAddress)
|
||||
await saveToStorage(key, tokens.toJS())
|
||||
await saveToStorage(ACTIVE_TOKENS_KEY, tokens.toJS())
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log('Error storing tokens in localstorage')
|
||||
}
|
||||
}
|
||||
|
||||
export const getActiveTokens = async (safeAddress: string): Promise<List<TokenProps>> => {
|
||||
const key = getActiveTokensKey(safeAddress)
|
||||
const data = await loadFromStorage(key)
|
||||
export const getActiveTokens = async (): Promise<Object<string, TokenProps>> => {
|
||||
const data = await loadFromStorage(ACTIVE_TOKENS_KEY)
|
||||
|
||||
return data ? List(data) : List()
|
||||
return data || {}
|
||||
}
|
||||
|
||||
export const getTokens = async (safeAddress: string): Promise<List<TokenProps>> => {
|
||||
const key = getTokensKey(safeAddress)
|
||||
const data = await loadFromStorage(key)
|
||||
export const getCustomTokens = async (): Promise<List<TokenProps>> => {
|
||||
const data = await loadFromStorage(CUSTOM_TOKENS_KEY)
|
||||
|
||||
return data ? List(data) : List()
|
||||
}
|
||||
|
||||
export const setToken = async (safeAddress: string, token: Token) => {
|
||||
const data: List<TokenProps> = await getTokens(safeAddress)
|
||||
const data: List<TokenProps> = await getCustomTokens()
|
||||
|
||||
try {
|
||||
const key = getTokensKey(safeAddress)
|
||||
await saveToStorage(key, data.push(token))
|
||||
await saveToStorage(CUSTOM_TOKENS_KEY, data.push(token))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log('Error adding token in localstorage')
|
||||
|
@ -46,12 +43,11 @@ export const setToken = async (safeAddress: string, token: Token) => {
|
|||
}
|
||||
|
||||
export const removeTokenFromStorage = async (safeAddress: string, token: Token) => {
|
||||
const data: List<TokenProps> = await getTokens(safeAddress)
|
||||
const data: List<TokenProps> = await getCustomTokens()
|
||||
|
||||
try {
|
||||
const index = data.indexOf(token)
|
||||
const key = getTokensKey(safeAddress)
|
||||
await saveToStorage(key, data.remove(index))
|
||||
await saveToStorage(CUSTOM_TOKENS_KEY, data.remove(index))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log('Error removing token in localstorage')
|
||||
|
@ -59,10 +55,10 @@ export const removeTokenFromStorage = async (safeAddress: string, token: Token)
|
|||
}
|
||||
|
||||
export const removeFromActiveTokens = async (safeAddress: string, token: Token) => {
|
||||
const activeTokens = await getActiveTokens(safeAddress)
|
||||
const activeTokens = await getActiveTokens()
|
||||
const index = activeTokens.findIndex(activeToken => activeToken.name === token.name)
|
||||
|
||||
if (index !== -1) {
|
||||
await setActiveTokens(safeAddress, activeTokens.delete(index))
|
||||
await saveActiveTokens(safeAddress, activeTokens.delete(index))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,19 +8,19 @@ import { loadFromStorage } from '~/utils/storage'
|
|||
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||
import { history } from '~/store'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import actions, { type Actions, type UpdateSafe } from './actions'
|
||||
import actions, { type Actions } from './actions'
|
||||
import Layout from '../components/Layout'
|
||||
import { FIELD_LOAD_NAME, FIELD_LOAD_ADDRESS } from '../components/fields'
|
||||
|
||||
type Props = SelectorProps & Actions
|
||||
|
||||
export const loadSafe = async (safeName: string, safeAddress: string, updateSafe: UpdateSafe) => {
|
||||
const safeRecord = await buildSafe(safeAddress, safeName)
|
||||
export const loadSafe = async (safeName: string, safeAddress: string, addSafe: Function) => {
|
||||
const safeProps = await buildSafe(safeAddress, safeName)
|
||||
|
||||
await updateSafe(safeRecord)
|
||||
await addSafe(safeProps)
|
||||
|
||||
const storedSafes = await loadFromStorage(SAFES_KEY) || {}
|
||||
storedSafes[safeAddress] = safeRecord.toJSON()
|
||||
const storedSafes = (await loadFromStorage(SAFES_KEY)) || {}
|
||||
storedSafes[safeAddress] = safeProps
|
||||
|
||||
saveSafes(storedSafes)
|
||||
}
|
||||
|
@ -28,11 +28,12 @@ export const loadSafe = async (safeName: string, safeAddress: string, updateSafe
|
|||
class Load extends React.Component<Props> {
|
||||
onLoadSafeSubmit = async (values: Object) => {
|
||||
try {
|
||||
const { updateSafe } = this.props
|
||||
const { addSafe } = this.props
|
||||
const safeName = values[FIELD_LOAD_NAME]
|
||||
const safeAddress = values[FIELD_LOAD_ADDRESS]
|
||||
|
||||
await loadSafe(safeName, safeAddress, updateSafe)
|
||||
await loadSafe(safeName, safeAddress, addSafe)
|
||||
|
||||
const url = `${SAFELIST_ADDRESS}/${safeAddress}`
|
||||
history.push(url)
|
||||
} catch (error) {
|
||||
|
@ -42,9 +43,7 @@ class Load extends React.Component<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
provider, network, userAddress,
|
||||
} = this.props
|
||||
const { provider, network, userAddress } = this.props
|
||||
|
||||
return (
|
||||
<Page>
|
||||
|
@ -59,4 +58,7 @@ class Load extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(Load)
|
||||
export default connect<Object, Object, ?Function, ?Object>(
|
||||
selector,
|
||||
actions,
|
||||
)(Load)
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
// @flow
|
||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||
|
||||
export type UpdateSafe = typeof updateSafe
|
||||
import { addSafe } from '~/routes/safe/store/actions/addSafe'
|
||||
|
||||
export type Actions = {
|
||||
updateSafe: typeof updateSafe,
|
||||
addSafe: Function,
|
||||
}
|
||||
|
||||
export default {
|
||||
updateSafe,
|
||||
addSafe,
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import * as React from 'react'
|
|||
import { List } from 'immutable'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import { connect } from 'react-redux'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Owner, makeOwner } from '~/routes/safe/store/model/owner'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { type Owner, makeOwner } from '~/routes/safe/store/models/owner'
|
||||
import { setOwners } from '~/utils/storage'
|
||||
import { getSafeEthereumInstance, createTransaction } from '~/logic/safe/safeFrontendOperations'
|
||||
import AddOwnerForm, { NAME_PARAM, OWNER_ADDRESS_PARAM, INCREASE_PARAM } from './AddOwnerForm'
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// @flow
|
||||
import enableToken from '~/logic/tokens/store/actions/enableToken'
|
||||
import disableToken from '~/logic/tokens/store/actions/disableToken'
|
||||
import fetchTokens from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import updateActiveTokens from '~/routes/safe/store/actions/updateActiveTokens'
|
||||
|
||||
export type Actions = {
|
||||
enableToken: typeof enableToken,
|
||||
disableToken: typeof disableToken,
|
||||
fetchTokens: Function,
|
||||
updateActiveTokens: Function,
|
||||
}
|
||||
|
||||
export default {
|
||||
enableToken,
|
||||
disableToken,
|
||||
fetchTokens,
|
||||
updateActiveTokens,
|
||||
}
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { List } from 'immutable'
|
||||
import { List, Set } from 'immutable'
|
||||
import classNames from 'classnames/bind'
|
||||
import SearchBar from 'material-ui-search-bar'
|
||||
import InfiniteScroll from 'react-infinite-scroll-component'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import MuiList from '@material-ui/core/List'
|
||||
import Img from '~/components/layout/Img'
|
||||
|
@ -23,6 +22,7 @@ import Divider from '~/components/layout/Divider'
|
|||
import Hairline from '~/components/layout/Hairline'
|
||||
import Spacer from '~/components/Spacer'
|
||||
import Row from '~/components/layout/Row'
|
||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import actions, { type Actions } from './actions'
|
||||
import TokenPlaceholder from './assets/token_placeholder.png'
|
||||
|
@ -33,10 +33,12 @@ type Props = Actions & {
|
|||
classes: Object,
|
||||
tokens: List<Token>,
|
||||
safeAddress: string,
|
||||
activeTokens: List<Token>,
|
||||
}
|
||||
|
||||
type State = {
|
||||
filter: string,
|
||||
activeTokensAddresses: Set<string>,
|
||||
}
|
||||
|
||||
const filterBy = (filter: string, tokens: List<Token>): List<Token> => tokens.filter(
|
||||
|
@ -45,9 +47,44 @@ const filterBy = (filter: string, tokens: List<Token>): List<Token> => tokens.fi
|
|||
|| token.name.toLowerCase().includes(filter.toLowerCase()),
|
||||
)
|
||||
|
||||
// OPTIMIZATION IDEA (Thanks Andre)
|
||||
// Calculate active tokens on component mount, store it in component state
|
||||
// After user closes modal, dispatch an action so we dont have 100500 actions
|
||||
// And selectors dont recalculate
|
||||
|
||||
class Tokens extends React.Component<Props, State> {
|
||||
state = {
|
||||
filter: '',
|
||||
activeTokensAddresses: Set([]),
|
||||
activeTokensCalculated: false,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { fetchTokens, safeAddress } = this.props
|
||||
|
||||
fetchTokens(safeAddress)
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
// I moved this logic here because if placed in ComponentDidMount
|
||||
// the user would see Switches switch and this method fires before the component mounts
|
||||
|
||||
if (!prevState.activeTokensCalculated) {
|
||||
const { activeTokens } = nextProps
|
||||
|
||||
return {
|
||||
activeTokensAddresses: Set(activeTokens.map(({ address }) => address)),
|
||||
activeTokensCalculated: true,
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { activeTokensAddresses } = this.state
|
||||
const { updateActiveTokens, safeAddress } = this.props
|
||||
|
||||
updateActiveTokens(safeAddress, activeTokensAddresses.toList())
|
||||
}
|
||||
|
||||
onCancelSearch = () => {
|
||||
|
@ -58,14 +95,17 @@ class Tokens extends React.Component<Props, State> {
|
|||
this.setState(() => ({ filter: value }))
|
||||
}
|
||||
|
||||
onSwitch = (token: Token) => (e: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
const { checked } = e.target
|
||||
const { safeAddress, enableToken, disableToken } = this.props
|
||||
onSwitch = (token: Token) => () => {
|
||||
const { activeTokensAddresses } = this.state
|
||||
|
||||
if (checked) {
|
||||
enableToken(safeAddress, token)
|
||||
if (activeTokensAddresses.has(token.address)) {
|
||||
this.setState({
|
||||
activeTokensAddresses: activeTokensAddresses.remove(token.address),
|
||||
})
|
||||
} else {
|
||||
disableToken(safeAddress, token)
|
||||
this.setState({
|
||||
activeTokensAddresses: activeTokensAddresses.add(token.address),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,7 +116,7 @@ class Tokens extends React.Component<Props, State> {
|
|||
|
||||
render() {
|
||||
const { onClose, classes, tokens } = this.props
|
||||
const { filter } = this.state
|
||||
const { filter, activeTokensAddresses } = this.state
|
||||
const searchClasses = {
|
||||
input: classes.searchInput,
|
||||
root: classes.searchRoot,
|
||||
|
@ -117,17 +157,23 @@ class Tokens extends React.Component<Props, State> {
|
|||
<Hairline />
|
||||
</Block>
|
||||
<MuiList className={classes.list}>
|
||||
{filteredTokens.map((token: Token) => (
|
||||
{filteredTokens.map((token: Token) => {
|
||||
const isActive = activeTokensAddresses.has(token.address)
|
||||
|
||||
return (
|
||||
<ListItem key={token.address} className={classes.token}>
|
||||
<ListItemIcon>
|
||||
<Img src={token.logoUri} height={28} alt={token.name} onError={this.setImageToPlaceholder} />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={token.symbol} secondary={token.name} />
|
||||
{token.address !== ETH_ADDRESS && (
|
||||
<ListItemSecondaryAction>
|
||||
<Switch onChange={this.onSwitch(token)} checked={token.status} />
|
||||
<Switch onChange={this.onSwitch(token)} checked={isActive} />
|
||||
</ListItemSecondaryAction>
|
||||
)}
|
||||
</ListItem>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
</MuiList>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
|
|
@ -17,9 +17,9 @@ export type BalanceRow = SortRow<BalanceData>
|
|||
|
||||
export const getBalanceData = (activeTokens: List<Token>): Array<BalanceRow> => {
|
||||
const rows = activeTokens.map((token: Token) => ({
|
||||
[BALANCE_TABLE_ASSET_ID]: token.get('name'),
|
||||
[BALANCE_TABLE_BALANCE_ID]: `${token.get('funds')} ${token.get('symbol')}`,
|
||||
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.get('funds')),
|
||||
[BALANCE_TABLE_ASSET_ID]: token.name,
|
||||
[BALANCE_TABLE_BALANCE_ID]: `${token.balance} ${token.symbol}`,
|
||||
[buildOrderFieldFrom(BALANCE_TABLE_BALANCE_ID)]: Number(token.balance),
|
||||
[FIXED]: token.get('symbol') === 'ETH',
|
||||
}))
|
||||
|
||||
|
|
|
@ -102,7 +102,12 @@ class Balances extends React.Component<Props, State> {
|
|||
handleClose={this.onHide('Token')}
|
||||
open={showToken}
|
||||
>
|
||||
<Tokens tokens={tokens} onClose={this.onHide('Token')} safeAddress={safeAddress} />
|
||||
<Tokens
|
||||
tokens={tokens}
|
||||
onClose={this.onHide('Token')}
|
||||
safeAddress={safeAddress}
|
||||
activeTokens={activeTokens}
|
||||
/>
|
||||
</Modal>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
@ -94,8 +94,8 @@ class Layout extends React.Component<Props, State> {
|
|||
if (!safe) {
|
||||
return <NoSafe provider={provider} text="Safe not found" />
|
||||
}
|
||||
// <GnoSafe safe={safe} tokens={activeTokens} userAddress={userAddress} />
|
||||
const address = safe.get('address')
|
||||
|
||||
const { address, ethBalance } = safe
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
@ -129,7 +129,13 @@ class Layout extends React.Component<Props, State> {
|
|||
</Row>
|
||||
<Hairline color="#c8ced4" />
|
||||
{tabIndex === 0 && (
|
||||
<Balances tokens={tokens} activeTokens={activeTokens} granted={granted} safeAddress={address} />
|
||||
<Balances
|
||||
ethBalance={ethBalance}
|
||||
tokens={tokens}
|
||||
activeTokens={activeTokens}
|
||||
granted={granted}
|
||||
safeAddress={address}
|
||||
/>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import * as React from 'react'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import { connect } from 'react-redux'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { getSafeEthereumInstance, createTransaction } from '~/logic/safe/safeFrontendOperations'
|
||||
import RemoveOwnerForm, { DECREASE_PARAM } from './RemoveOwnerForm'
|
||||
import Review from './Review'
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { List } from 'immutable'
|
||||
import { createStructuredSelector, createSelector } from 'reselect'
|
||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
|
||||
|
||||
const pendingTransactionsSelector = createSelector(
|
||||
|
|
|
@ -15,7 +15,7 @@ import Delete from '@material-ui/icons/Delete'
|
|||
import Person from '@material-ui/icons/Person'
|
||||
import ExpandLess from '@material-ui/icons/ExpandLess'
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore'
|
||||
import { type OwnerProps } from '~/routes/safe/store/model/owner'
|
||||
import { type OwnerProps } from '~/routes/safe/store/models/owner'
|
||||
import { type WithStyles } from '~/theme/mui'
|
||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import Bold from '~/components/layout/Bold'
|
|||
import Img from '~/components/layout/Img'
|
||||
import Paragraph from '~/components/layout/Paragraph'
|
||||
import Row from '~/components/layout/Row'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
|
||||
import Transactions from '~/routes/safe/component/Transactions'
|
||||
|
|
|
@ -4,7 +4,7 @@ import { BigNumber } from 'bignumber.js'
|
|||
import { connect } from 'react-redux'
|
||||
import Stepper from '~/components/Stepper'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { isEther } from '~/logic/tokens/utils/tokens'
|
||||
|
|
|
@ -6,7 +6,7 @@ import OpenPaper from '~/components/Stepper/OpenPaper'
|
|||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import { composeValidators, minValue, maxValue, mustBeInteger, required } from '~/components/forms/validator'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
|
||||
export const THRESHOLD_PARAM = 'threshold'
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as React from 'react'
|
|||
import Stepper from '~/components/Stepper'
|
||||
import { connect } from 'react-redux'
|
||||
import { getSafeEthereumInstance, createTransaction } from '~/logic/safe/safeFrontendOperations'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import ThresholdForm, { THRESHOLD_PARAM } from './ThresholdForm'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import actions, { type Actions } from './actions'
|
||||
|
|
|
@ -14,7 +14,7 @@ import Person from '@material-ui/icons/Person'
|
|||
import ExpandLess from '@material-ui/icons/ExpandLess'
|
||||
import ExpandMore from '@material-ui/icons/ExpandMore'
|
||||
import { type WithStyles } from '~/theme/mui'
|
||||
import { type Confirmation, type ConfirmationProps } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Confirmation, type ConfirmationProps } from '~/routes/safe/store/models/confirmation'
|
||||
|
||||
const styles = {
|
||||
nested: {
|
||||
|
|
|
@ -9,7 +9,7 @@ import ListItemText from '~/components/List/ListItemText'
|
|||
import Avatar from '@material-ui/core/Avatar'
|
||||
import Group from '@material-ui/icons/Group'
|
||||
import MailOutline from '@material-ui/icons/MailOutline'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import Confirmations from './Confirmations'
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -16,11 +16,11 @@ import Atm from '@material-ui/icons/LocalAtm'
|
|||
import DoneAll from '@material-ui/icons/DoneAll'
|
||||
import CompareArrows from '@material-ui/icons/CompareArrows'
|
||||
import Collapsed from '~/routes/safe/component/Transactions/Collapsed'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import Hairline from '~/components/layout/Hairline/index'
|
||||
import Button from '~/components/layout/Button'
|
||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
|
||||
type Props = Open &
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
import { createStructuredSelector } from 'reselect'
|
||||
import { confirmationsTransactionSelector } from '~/routes/safe/store/selectors/index'
|
||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import { type GlobalState } from '~/store'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||
|
||||
export type SelectorProps = {
|
||||
confirmed: confirmationsTransactionSelector,
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
import * as React from 'react'
|
||||
import { List } from 'immutable'
|
||||
import { connect } from 'react-redux'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import NoTransactions from '~/routes/safe/component/Transactions/NoTransactions'
|
||||
import GnoTransaction from '~/routes/safe/component/Transactions/Transaction'
|
||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import { processTransaction } from '~/logic/safe/safeFrontendOperations'
|
||||
import selector, { type SelectorProps } from './selector'
|
||||
import actions, { type Actions } from './actions'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import { safeTransactionsSelector } from '~/routes/safe/store/selectors/index'
|
||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
// @flow
|
||||
import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
|
||||
import { fetchTokens } from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import loadActiveTokens from '~/logic/tokens/store/actions/loadActiveTokens'
|
||||
import fetchTokenBalances from '~/routes/safe/store/actions/fetchTokenBalances'
|
||||
|
||||
export type Actions = {
|
||||
fetchSafe: typeof fetchSafe,
|
||||
fetchTokens: typeof fetchTokens,
|
||||
loadActiveTokens: typeof loadActiveTokens,
|
||||
fetchTokenBalances: typeof fetchTokenBalances,
|
||||
}
|
||||
|
||||
export default {
|
||||
fetchSafe,
|
||||
fetchTokens,
|
||||
loadActiveTokens,
|
||||
fetchTokenBalances,
|
||||
}
|
||||
|
|
|
@ -6,34 +6,34 @@ import Layout from '~/routes/safe/component/Layout'
|
|||
import selector, { type SelectorProps } from './selector'
|
||||
import actions, { type Actions } from './actions'
|
||||
|
||||
type Props = Actions & SelectorProps & {
|
||||
export type Props = Actions &
|
||||
SelectorProps & {
|
||||
granted: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 15000
|
||||
|
||||
class SafeView extends React.PureComponent<Props> {
|
||||
class SafeView extends React.Component<Props> {
|
||||
componentDidMount() {
|
||||
const { safeUrl, fetchTokens, fetchSafe } = this.props
|
||||
const {
|
||||
fetchSafe, loadActiveTokens, activeTokens, safeUrl, fetchTokenBalances,
|
||||
} = this.props
|
||||
|
||||
fetchSafe(safeUrl)
|
||||
fetchTokens(safeUrl)
|
||||
// loadActiveTokens(safeUrl)
|
||||
fetchTokenBalances(safeUrl, activeTokens)
|
||||
|
||||
this.intervalId = setInterval(() => {
|
||||
fetchTokens(safeUrl)
|
||||
fetchSafe(safeUrl)
|
||||
// update in another function so it uses up-to-date props values
|
||||
this.checkForUpdates()
|
||||
}, TIMEOUT)
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { safe, fetchTokens } = this.props
|
||||
const { activeTokens } = this.props
|
||||
|
||||
if (prevProps.safe) {
|
||||
return
|
||||
}
|
||||
|
||||
if (safe) {
|
||||
const safeAddress = safe.get('address')
|
||||
fetchTokens(safeAddress)
|
||||
if (activeTokens.size > prevProps.activeTokens.size) {
|
||||
this.checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,15 @@ class SafeView extends React.PureComponent<Props> {
|
|||
clearInterval(this.intervalId)
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
const {
|
||||
safeUrl, activeTokens, fetchSafe, fetchTokenBalances,
|
||||
} = this.props
|
||||
|
||||
fetchSafe(safeUrl, true)
|
||||
fetchTokenBalances(safeUrl, activeTokens)
|
||||
}
|
||||
|
||||
intervalId: IntervalID
|
||||
|
||||
render() {
|
||||
|
@ -64,4 +73,7 @@ class SafeView extends React.PureComponent<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(SafeView)
|
||||
export default connect<Object, Object, ?Function, ?Object>(
|
||||
selector,
|
||||
actions,
|
||||
)(SafeView)
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { List, Map } from 'immutable'
|
||||
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
|
||||
import { safeSelector, type RouterProps, type SafeSelectorProps } from '~/routes/safe/store/selectors'
|
||||
import {
|
||||
safeSelector,
|
||||
safeActiveTokensSelector,
|
||||
safeBalancesSelector,
|
||||
type RouterProps,
|
||||
type SafeSelectorProps,
|
||||
} from '~/routes/safe/store/selectors'
|
||||
import { providerNameSelector, userAccountSelector, networkSelector } from '~/logic/wallets/store/selectors'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { type Owner } from '~/routes/safe/store/models/owner'
|
||||
import { type GlobalState } from '~/store'
|
||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
import { activeTokensSelector, orderedTokenListSelector } from '~/logic/tokens/store/selectors'
|
||||
import { orderedTokenListSelector, tokensSelector } from '~/logic/tokens/store/selectors'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import { type TokenBalance } from '~/routes/safe/store/models/tokenBalance'
|
||||
import { safeParamAddressSelector } from '../store/selectors'
|
||||
import { getEthAsToken } from '~/logic/tokens/utils/tokenHelpers'
|
||||
|
||||
export type SelectorProps = {
|
||||
safe: SafeSelectorProps,
|
||||
|
@ -42,11 +50,52 @@ export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = crea
|
|||
},
|
||||
)
|
||||
|
||||
export default createStructuredSelector({
|
||||
type UserToken = {
|
||||
address: string,
|
||||
balance: string,
|
||||
}
|
||||
|
||||
const safeEthAsTokenSelector: Selector<GlobalState, RouterProps, ?Token> = createSelector(
|
||||
safeSelector,
|
||||
(safe: Safe) => {
|
||||
if (!safe) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return getEthAsToken(safe.ethBalance)
|
||||
},
|
||||
)
|
||||
|
||||
const extendedSafeTokensSelector: Selector<GlobalState, RouterProps, List<Token>> = createSelector(
|
||||
safeActiveTokensSelector,
|
||||
safeBalancesSelector,
|
||||
tokensSelector,
|
||||
safeEthAsTokenSelector,
|
||||
(safeTokens: List<string>, balances: List<TokenBalance>, tokensList: Map<string, Token>, ethAsToken: Token) => {
|
||||
const extendedTokens = Map().withMutations((map) => {
|
||||
safeTokens.forEach((tokenAddress: string) => {
|
||||
const baseToken = tokensList.get(tokenAddress)
|
||||
const tokenBalance = balances.find(tknBalance => tknBalance.address === tokenAddress)
|
||||
|
||||
if (baseToken) {
|
||||
map.set(tokenAddress, baseToken.set('balance', tokenBalance ? tokenBalance.balance : '0'))
|
||||
}
|
||||
})
|
||||
|
||||
if (ethAsToken) {
|
||||
map.set(ethAsToken.address, ethAsToken)
|
||||
}
|
||||
})
|
||||
|
||||
return extendedTokens.toList()
|
||||
},
|
||||
)
|
||||
|
||||
export default createStructuredSelector<Object, *>({
|
||||
safe: safeSelector,
|
||||
provider: providerNameSelector,
|
||||
tokens: orderedTokenListSelector,
|
||||
activeTokens: activeTokensSelector,
|
||||
activeTokens: extendedSafeTokensSelector,
|
||||
granted: grantedSelector,
|
||||
userAddress: userAccountSelector,
|
||||
network: networkSelector,
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { createAction } from 'redux-actions'
|
||||
import { type Safe, makeSafe } from '~/routes/safe/store/model/safe'
|
||||
import { saveSafes, setOwners } from '~/logic/safe/utils'
|
||||
import { makeOwner, type Owner } from '~/routes/safe/store/model/owner'
|
||||
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { safesMapSelector } from '~/routes/safeList/store/selectors/index'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type GlobalState } from '~/store'
|
||||
import SafeRecord, { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { makeOwner, type Owner } from '~/routes/safe/store/models/owner'
|
||||
|
||||
export const ADD_SAFE = 'ADD_SAFE'
|
||||
|
||||
|
@ -20,7 +18,7 @@ type ActionReturn = {
|
|||
safe: Safe,
|
||||
}
|
||||
|
||||
export const addSafe = createAction<string, *, *>(
|
||||
export const addSafe = createAction<string, Function, ActionReturn>(
|
||||
ADD_SAFE,
|
||||
(safe: Safe): ActionReturn => ({
|
||||
safe,
|
||||
|
@ -33,21 +31,15 @@ const saveSafe = (
|
|||
threshold: number,
|
||||
ownersName: string[],
|
||||
ownersAddress: string[],
|
||||
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
|
||||
) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const owners: List<Owner> = buildOwnersFrom(ownersName, ownersAddress)
|
||||
const state: GlobalState = getState()
|
||||
|
||||
const safe: Safe = makeSafe({
|
||||
const safe: Safe = SafeRecord({
|
||||
name,
|
||||
address,
|
||||
threshold,
|
||||
owners,
|
||||
})
|
||||
const safes = safesMapSelector(state)
|
||||
const newSafes = safes.set(address, safe)
|
||||
|
||||
setOwners(address, owners)
|
||||
saveSafes(newSafes.toJSON())
|
||||
|
||||
dispatch(addSafe(safe))
|
||||
}
|
||||
|
|
|
@ -2,14 +2,18 @@
|
|||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { List, Map } from 'immutable'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { makeOwner } from '~/routes/safe/store/model/owner'
|
||||
import { type SafeProps, makeSafe } from '~/routes/safe/store/model/safe'
|
||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||
import { makeOwner } from '~/routes/safe/store/models/owner'
|
||||
import type { SafeProps } from '~/routes/safe/store/models/safe'
|
||||
import { addSafe } from '~/routes/safe/store/actions/addSafe'
|
||||
import { getOwners, getSafeName } from '~/logic/safe/utils'
|
||||
import { getGnosisSafeContract } from '~/logic/contracts/safeContracts'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { getWeb3, getBalanceInEtherOf } from '~/logic/wallets/getWeb3'
|
||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||
|
||||
const buildOwnersFrom = (safeOwners: string[], storedOwners: Map<string, string>) => safeOwners.map((ownerAddress: string) => {
|
||||
const buildOwnersFrom = (
|
||||
safeOwners: string[],
|
||||
storedOwners: Map<string, string>, // eslint-disable-next-line
|
||||
) => safeOwners.map((ownerAddress: string) => {
|
||||
const ownerName = storedOwners.get(ownerAddress.toLowerCase()) || 'UNKNOWN'
|
||||
return makeOwner({ name: ownerName, address: ownerAddress })
|
||||
})
|
||||
|
@ -18,6 +22,7 @@ export const buildSafe = async (safeAddress: string, safeName: string) => {
|
|||
const web3 = getWeb3()
|
||||
const SafeContract = await getGnosisSafeContract(web3)
|
||||
const gnosisSafe = await SafeContract.at(safeAddress)
|
||||
const ethBalance = await getBalanceInEtherOf(safeAddress)
|
||||
|
||||
const threshold = Number(await gnosisSafe.getThreshold())
|
||||
const owners = List(buildOwnersFrom(await gnosisSafe.getOwners(), await getOwners(safeAddress)))
|
||||
|
@ -27,17 +32,22 @@ export const buildSafe = async (safeAddress: string, safeName: string) => {
|
|||
name: safeName,
|
||||
threshold,
|
||||
owners,
|
||||
ethBalance,
|
||||
}
|
||||
|
||||
return makeSafe(safe)
|
||||
return safe
|
||||
}
|
||||
|
||||
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
export default (safeAddress: string, update: boolean = false) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
try {
|
||||
const safeName = await getSafeName(safeAddress) || 'LOADED SAFE'
|
||||
const safeRecord = await buildSafe(safeAddress, safeName)
|
||||
const safeName = (await getSafeName(safeAddress)) || 'LOADED SAFE'
|
||||
const safeProps: SafeProps = await buildSafe(safeAddress, safeName)
|
||||
|
||||
return dispatch(updateSafe(safeRecord))
|
||||
if (update) {
|
||||
dispatch(updateSafe(safeProps))
|
||||
} else {
|
||||
dispatch(addSafe(safeProps))
|
||||
}
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error while updating safe information: ', err)
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
// @flow
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { List } from 'immutable'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { type Token } from '~/logic/tokens/store/model/token'
|
||||
import TokenBalanceRecord from '~/routes/safe/store/models/tokenBalance'
|
||||
import { getStandardTokenContract } from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import updateSafe from './updateSafe'
|
||||
import { ETH_ADDRESS } from '~/logic/tokens/utils/tokenHelpers'
|
||||
|
||||
export const calculateBalanceOf = async (tokenAddress: string, safeAddress: string, decimals: number = 18) => {
|
||||
if (tokenAddress === ETH_ADDRESS) {
|
||||
return '0'
|
||||
}
|
||||
|
||||
const erc20Token = await getStandardTokenContract()
|
||||
let balance = 0
|
||||
|
||||
try {
|
||||
const token = await erc20Token.at(tokenAddress)
|
||||
balance = await token.balanceOf(safeAddress)
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch token balances: ', err)
|
||||
}
|
||||
|
||||
return new BigNumber(balance).div(10 ** decimals).toString()
|
||||
}
|
||||
|
||||
const fetchTokenBalances = (safeAddress: string, tokens: List<Token>) => async (
|
||||
dispatch: ReduxDispatch<GlobalState>,
|
||||
) => {
|
||||
if (!safeAddress || !tokens || !tokens.size) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const withBalances = await Promise.all(
|
||||
tokens
|
||||
.map(async token => TokenBalanceRecord({
|
||||
address: token.address,
|
||||
balance: await calculateBalanceOf(token.address, safeAddress, token.decimals),
|
||||
})),
|
||||
)
|
||||
|
||||
dispatch(updateSafe({ address: safeAddress, balances: List(withBalances) }))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error('Error while loading active tokens from storage:', err)
|
||||
}
|
||||
}
|
||||
|
||||
export default fetchTokenBalances
|
|
@ -3,9 +3,9 @@ import { List, Map } from 'immutable'
|
|||
import axios from 'axios'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { makeOwner } from '~/routes/safe/store/model/owner'
|
||||
import { makeTransaction, type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { makeConfirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { makeOwner } from '~/routes/safe/store/models/owner'
|
||||
import { makeTransaction, type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import { makeConfirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import { loadSafeSubjects } from '~/utils/storage/transactions'
|
||||
import { buildTxServiceUrlFrom, type TxServiceType } from '~/logic/safe/safeTxHistory'
|
||||
import { getOwners } from '~/logic/safe/utils'
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { SAFES_KEY } from '~/logic/safe/utils'
|
||||
import { type SafeProps } from '~/routes/safe/store/model/safe'
|
||||
import { type SafeProps } from '~/routes/safe/store/models/safe'
|
||||
import { loadFromStorage } from '~/utils/storage'
|
||||
import { addSafe } from './addSafe'
|
||||
import { buildSafe } from '~/routes/safe/store/reducer/safe'
|
||||
|
||||
export default () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const loadSafesFromStorage = () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
try {
|
||||
const safes: ?{ [string]: SafeProps } = await loadFromStorage(SAFES_KEY)
|
||||
|
||||
|
@ -23,3 +23,5 @@ export default () => async (dispatch: ReduxDispatch<GlobalState>) => {
|
|||
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
export default loadSafesFromStorage
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import { type GlobalState } from '~/store'
|
||||
import updateSafe from './updateSafe'
|
||||
|
||||
// the selector uses ownProps argument/router props to get the address of the safe
|
||||
// so in order to use it I had to recreate the same structure
|
||||
// const generateMatchProps = (safeAddress: string) => ({
|
||||
// match: {
|
||||
// params: {
|
||||
// [SAFE_PARAM_ADDRESS]: safeAddress,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
|
||||
const updateActiveTokens = (safeAddress: string, activeTokens: List<string>) => async (
|
||||
dispatch: ReduxDispatch<GlobalState>,
|
||||
) => {
|
||||
dispatch(updateSafe({ address: safeAddress, activeTokens }))
|
||||
}
|
||||
|
||||
export default updateActiveTokens
|
|
@ -1,8 +0,0 @@
|
|||
// @flow
|
||||
import { createAction } from 'redux-actions'
|
||||
|
||||
export const UPDATE_SAFES = 'UPDATE_SAFES'
|
||||
|
||||
const updateSafesInBatch = createAction<string, *>(UPDATE_SAFES)
|
||||
|
||||
export default updateSafesInBatch
|
|
@ -0,0 +1,48 @@
|
|||
// @flow
|
||||
import { ADD_SAFE } from '~/routes/safe/store/actions/addSafe'
|
||||
import { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
|
||||
import type { Store, AnyAction } from 'redux'
|
||||
import { type GlobalState } from '~/store/'
|
||||
import { saveSafes, setOwners } from '~/logic/safe/utils'
|
||||
import { safesMapSelector } from '~/routes/safeList/store/selectors'
|
||||
import { getActiveTokensAddressesForAllSafes } from '~/routes/safe/store/selectors'
|
||||
import { tokensSelector } from '~/logic/tokens/store/selectors'
|
||||
import type { Token } from '~/logic/tokens/store/model/token'
|
||||
import { saveActiveTokens } from '~/logic/tokens/utils/tokensStorage'
|
||||
|
||||
const watchedActions = [ADD_SAFE, UPDATE_SAFE]
|
||||
|
||||
const safeStorageMware = (store: Store<GlobalState>) => (next: Function) => async (action: AnyAction) => {
|
||||
const handledAction = next(action)
|
||||
|
||||
if (watchedActions.includes(action.type)) {
|
||||
const state: GlobalState = store.getState()
|
||||
const safes = safesMapSelector(state)
|
||||
saveSafes(safes.toJSON())
|
||||
|
||||
// recalculate active tokens
|
||||
if (action.payload.activeTokens) {
|
||||
const tokens = tokensSelector(state)
|
||||
const activeTokenAddresses = getActiveTokensAddressesForAllSafes(state)
|
||||
|
||||
const activeTokens = tokens.withMutations((map) => {
|
||||
map.forEach((token: Token) => {
|
||||
if (!activeTokenAddresses.has(token.address)) {
|
||||
map.remove(token.address)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
saveActiveTokens(activeTokens)
|
||||
}
|
||||
|
||||
if (action.type === ADD_SAFE) {
|
||||
const { safe } = action.payload
|
||||
setOwners(safe.address, safe.owners)
|
||||
}
|
||||
}
|
||||
|
||||
return handledAction
|
||||
}
|
||||
|
||||
export default safeStorageMware
|
|
@ -1,22 +0,0 @@
|
|||
// @flow
|
||||
import { List, Record } from 'immutable'
|
||||
import type { RecordFactory, RecordOf } from 'immutable'
|
||||
import type { Owner } from '~/routes/safe/store/model/owner'
|
||||
|
||||
export type SafeProps = {
|
||||
name: string,
|
||||
address: string,
|
||||
threshold: number,
|
||||
owners: List<Owner>,
|
||||
}
|
||||
|
||||
export const makeSafe: RecordFactory<SafeProps> = Record({
|
||||
name: '',
|
||||
address: '',
|
||||
threshold: 0,
|
||||
owners: List([]),
|
||||
})
|
||||
|
||||
export type Safe = RecordOf<SafeProps>
|
||||
|
||||
// Useage const someRecord: Safe = makeSafe({ name: ... })
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { Record } from 'immutable'
|
||||
import type { RecordFactory, RecordOf } from 'immutable'
|
||||
import { makeOwner, type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { makeOwner, type Owner } from '~/routes/safe/store/models/owner'
|
||||
import { type TxServiceType } from '~/logic/safe/safeTxHistory'
|
||||
|
||||
export type ConfirmationProps = {
|
|
@ -0,0 +1,29 @@
|
|||
// @flow
|
||||
import { List, Record } from 'immutable'
|
||||
import type { RecordFactory, RecordOf } from 'immutable'
|
||||
import type { Owner } from '~/routes/safe/store/models/owner'
|
||||
import TokenBalance from '~/routes/safe/store/models/tokenBalance'
|
||||
|
||||
export type SafeProps = {
|
||||
name: string,
|
||||
address: string,
|
||||
threshold: number,
|
||||
owners: List<Owner>,
|
||||
balances?: List<TokenBalance>,
|
||||
activeTokens?: List<string>,
|
||||
ethBalance?: string,
|
||||
}
|
||||
|
||||
const SafeRecord: RecordFactory<SafeProps> = Record({
|
||||
name: '',
|
||||
address: '',
|
||||
threshold: 0,
|
||||
ethBalance: 0,
|
||||
owners: List([]),
|
||||
activeTokens: List([]),
|
||||
balances: List([]),
|
||||
})
|
||||
|
||||
export type Safe = RecordOf<SafeProps>
|
||||
|
||||
export default SafeRecord
|
|
@ -0,0 +1,17 @@
|
|||
// @flow
|
||||
import { Record } from 'immutable'
|
||||
import type { RecordFactory, RecordOf } from 'immutable'
|
||||
|
||||
export type TokenBalanceProps = {
|
||||
address: string,
|
||||
balance: string,
|
||||
}
|
||||
|
||||
const TokenBalanceRecord: RecordFactory<TokenBalanceProps> = Record({
|
||||
address: '',
|
||||
balance: '0',
|
||||
})
|
||||
|
||||
export type TokenBalance = RecordOf<TokenBalanceProps>
|
||||
|
||||
export default TokenBalanceRecord
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { List, Record } from 'immutable'
|
||||
import type { RecordFactory, RecordOf } from 'immutable'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||
|
||||
export type TransactionProps = {
|
||||
name: string,
|
|
@ -1,9 +1,10 @@
|
|||
// @flow
|
||||
import { Map } from 'immutable'
|
||||
import { Map, List } from 'immutable'
|
||||
import { handleActions, type ActionType } from 'redux-actions'
|
||||
import { ADD_SAFE, buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||
import { type Safe, type SafeProps, makeSafe } from '~/routes/safe/store/model/safe'
|
||||
import { type OwnerProps } from '~/routes/safe/store/model/owner'
|
||||
import SafeRecord, { type Safe, type SafeProps } from '~/routes/safe/store/models/safe'
|
||||
import TokenBalance from '~/routes/safe/store/models/tokenBalance'
|
||||
import { type OwnerProps } from '~/routes/safe/store/models/owner'
|
||||
import { loadFromStorage } from '~/utils/storage'
|
||||
import { SAFES_KEY } from '~/logic/safe/utils'
|
||||
import { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
|
||||
|
@ -16,15 +17,17 @@ export const buildSafe = (storedSafe: SafeProps) => {
|
|||
const names = storedSafe.owners.map((owner: OwnerProps) => owner.name)
|
||||
const addresses = storedSafe.owners.map((owner: OwnerProps) => owner.address)
|
||||
const owners = buildOwnersFrom(Array.from(names), Array.from(addresses))
|
||||
const activeTokens = List(storedSafe.activeTokens)
|
||||
const balances = storedSafe.balances.map(balance => TokenBalance(balance))
|
||||
|
||||
const safe: SafeProps = {
|
||||
address: storedSafe.address,
|
||||
name: storedSafe.name,
|
||||
threshold: storedSafe.threshold,
|
||||
...storedSafe,
|
||||
owners,
|
||||
balances,
|
||||
activeTokens,
|
||||
}
|
||||
|
||||
return makeSafe(safe)
|
||||
return safe
|
||||
}
|
||||
|
||||
const buildSafesFrom = (loadedSafes: Object): Map<string, Safe> => {
|
||||
|
@ -35,7 +38,7 @@ const buildSafesFrom = (loadedSafes: Object): Map<string, Safe> => {
|
|||
const safeRecords = keys.map((address: string) => buildSafe(loadedSafes[address]))
|
||||
|
||||
return safes.withMutations(async (map) => {
|
||||
safeRecords.forEach((safe: Safe) => map.set(safe.get('address'), safe))
|
||||
safeRecords.forEach((safe: SafeProps) => map.set(safe.address, safe))
|
||||
})
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
|
@ -56,19 +59,22 @@ export default handleActions<State, *>(
|
|||
{
|
||||
[UPDATE_SAFE]: (state: State, action: ActionType<Function>): State => {
|
||||
const safe = action.payload
|
||||
const safeAddress = safe.get('address')
|
||||
const safeAddress = safe.address
|
||||
|
||||
const hasSafe = !!state.get(safeAddress)
|
||||
if (hasSafe) {
|
||||
return state.update(safeAddress, prevSafe => (prevSafe.equals(safe) ? prevSafe : safe))
|
||||
}
|
||||
|
||||
return state.set(safeAddress, safe)
|
||||
return state.update(safeAddress, prevSafe => prevSafe.merge(safe))
|
||||
},
|
||||
[ADD_SAFE]: (state: State, action: ActionType<Function>): State => {
|
||||
const { safe }: { safe: Safe } = action.payload
|
||||
const { safe }: { safe: SafeProps } = action.payload
|
||||
|
||||
return state.set(safe.address, safe)
|
||||
// if you add a new safe it needs to be set as a record
|
||||
// in case of update it shouldn't, because a record would be initialized
|
||||
// with initial props and it would overwrite existing ones
|
||||
|
||||
if (state.has(safe.address)) {
|
||||
return state.update(safe.address, prevSafe => prevSafe.merge(safe))
|
||||
}
|
||||
|
||||
return state.set(safe.address, SafeRecord(safe))
|
||||
},
|
||||
},
|
||||
Map(),
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { List, Map } from 'immutable'
|
||||
import { handleActions, type ActionType } from 'redux-actions'
|
||||
import addTransactions, { ADD_TRANSACTIONS } from '~/routes/safe/store/actions/addTransactions'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
|
||||
export const TRANSACTIONS_REDUCER_ID = 'transactions'
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
// @flow
|
||||
import { Map, List } from 'immutable'
|
||||
import { Map, List, Set } from 'immutable'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { SAFE_PARAM_ADDRESS } from '~/routes/routes'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { safesMapSelector } from '~/routes/safeList/store/selectors'
|
||||
import { type State as TransactionsState, TRANSACTIONS_REDUCER_ID } from '~/routes/safe/store/reducer/transactions'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import { safesListSelector } from '~/routes/safeList/store/selectors/'
|
||||
|
||||
export type RouterProps = {
|
||||
match: Match,
|
||||
|
@ -78,6 +79,44 @@ export const safeSelector: Selector<GlobalState, RouterProps, SafeSelectorProps>
|
|||
},
|
||||
)
|
||||
|
||||
export default createStructuredSelector({
|
||||
export const safeActiveTokensSelector: Selector<GlobalState, RouterProps, List<string>> = createSelector(
|
||||
safeSelector,
|
||||
(safe: Safe) => {
|
||||
if (!safe) {
|
||||
return List()
|
||||
}
|
||||
|
||||
return safe.activeTokens
|
||||
},
|
||||
)
|
||||
|
||||
export const safeBalancesSelector: Selector<GlobalState, RouterProps, Map<string, string>> = createSelector(
|
||||
safeSelector,
|
||||
(safe: Safe) => {
|
||||
if (!safe) {
|
||||
return List()
|
||||
}
|
||||
|
||||
return safe.balances
|
||||
},
|
||||
)
|
||||
|
||||
export const getActiveTokensAddressesForAllSafes: Selector<GlobalState, any, Set<string>> = createSelector(
|
||||
safesListSelector,
|
||||
(safes: List<Safe>) => {
|
||||
const addresses = Set().withMutations((set) => {
|
||||
safes.forEach((safe: Safe) => {
|
||||
safe.activeTokens.forEach((tokenAddress) => {
|
||||
set.add(tokenAddress)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return addresses
|
||||
},
|
||||
)
|
||||
|
||||
export default createStructuredSelector<Object, *>({
|
||||
safe: safeSelector,
|
||||
tokens: safeActiveTokensSelector,
|
||||
})
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// @flow
|
||||
import { makeSafe, type Safe } from '~/routes/safe/store/model/safe'
|
||||
import SafeRecord, { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||
|
||||
class SafeBuilder {
|
||||
safe: Safe
|
||||
|
||||
constructor() {
|
||||
this.safe = makeSafe()
|
||||
this.safe = SafeRecord()
|
||||
}
|
||||
|
||||
withAddress(address: string) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Map } from 'immutable'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Map } from 'immutable'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { safeSelector } from '../selectors'
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { List } from 'immutable'
|
||||
import * as React from 'react'
|
||||
import NoSafe from '~/components/NoSafe'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import SafeTable from '~/routes/safeList/components/SafeTable'
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -6,7 +6,7 @@ import Link from '~/components/layout/Link'
|
|||
import Table, {
|
||||
TableBody, TableCell, TableHead, TableRow,
|
||||
} from '~/components/layout/Table'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { SAFELIST_ADDRESS } from '~/routes/routes'
|
||||
|
||||
type Props = {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { List } from 'immutable'
|
|||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import Page from '~/components/layout/Page'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import Layout from '../components/Layout'
|
||||
import selector from './selector'
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// @flow
|
||||
import { createStructuredSelector } from 'reselect'
|
||||
import { safesByOwnerSelector } from '~/routes/safeList/store/selectors'
|
||||
import { safesListSelector } from '~/routes/safeList/store/selectors'
|
||||
import { providerNameSelector } from '~/logic/wallets/store/selectors'
|
||||
|
||||
export default createStructuredSelector({
|
||||
safes: safesByOwnerSelector,
|
||||
export default createStructuredSelector<Object, *>({
|
||||
safes: safesListSelector,
|
||||
provider: providerNameSelector,
|
||||
})
|
||||
|
|
|
@ -2,23 +2,12 @@
|
|||
import { List, Map } from 'immutable'
|
||||
import { createSelector, type Selector } from 'reselect'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { userAccountSelector } from '~/logic/wallets/store/selectors'
|
||||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
|
||||
export const safesMapSelector = (state: GlobalState): Map<string, Safe> => state[SAFE_REDUCER_ID]
|
||||
|
||||
const safesListSelector: Selector<GlobalState, {}, List<Safe>> = createSelector(
|
||||
export const safesListSelector: Selector<GlobalState, {}, List<Safe>> = createSelector(
|
||||
safesMapSelector,
|
||||
(safes: Map<string, Safe>): List<Safe> => safes.toList(),
|
||||
)
|
||||
|
||||
export const safesByOwnerSelector: Selector<GlobalState, {}, List<Safe>> = createSelector(
|
||||
userAccountSelector,
|
||||
safesListSelector,
|
||||
(userAddress: string, safes: List<Safe>): List<Safe> => safes.filter(
|
||||
(safe: Safe) => safe.owners.filter((owner: Owner) => sameAddress(owner.get('address'), userAddress)).count() > 0,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { List, Map } from 'immutable'
|
||||
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { getProviderInfo } from '~/logic/wallets/getWeb3'
|
||||
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
|
||||
import { PROVIDER_REDUCER_ID } from '~/logic/wallets/store/reducer/provider'
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
import thunk from 'redux-thunk'
|
||||
import provider, { PROVIDER_REDUCER_ID, type State as ProviderState } from '~/logic/wallets/store/reducer/provider'
|
||||
import safe, { SAFE_REDUCER_ID, type State as SafeState } from '~/routes/safe/store/reducer/safe'
|
||||
import safeStorage from '~/routes/safe/store/middleware/safeStorage'
|
||||
import tokens, { TOKEN_REDUCER_ID, type State as TokensState } from '~/logic/tokens/store/reducer/tokens'
|
||||
import transactions, {
|
||||
type State as TransactionsState,
|
||||
|
@ -17,7 +18,7 @@ export const history = createBrowserHistory()
|
|||
|
||||
// eslint-disable-next-line
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||
const finalCreateStore = composeEnhancers(applyMiddleware(thunk, routerMiddleware(history)))
|
||||
const finalCreateStore = composeEnhancers(applyMiddleware(thunk, routerMiddleware(history), safeStorage))
|
||||
|
||||
export type GlobalState = {
|
||||
providers: ProviderState,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import { makeSafe, type Safe } from '~/routes/safe/store/model/safe'
|
||||
import SafeRecord, { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import addSafe, { buildOwnersFrom } from '~/routes/safe/store/actions/addSafe'
|
||||
import {
|
||||
FIELD_NAME,
|
||||
|
@ -18,7 +18,7 @@ class SafeBuilder {
|
|||
safe: Safe
|
||||
|
||||
constructor() {
|
||||
this.safe = makeSafe()
|
||||
this.safe = SafeRecord()
|
||||
}
|
||||
|
||||
withAddress(address: string) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// @flow
|
||||
import { Map, List } from 'immutable'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import updateSafe from '~/routes/safe/store/actions/updateSafe'
|
||||
import { loadSafe } from '~/routes/load/container/Load'
|
||||
import { safesMapSelector } from '~/routes/safeList/store/selectors'
|
||||
import { makeOwner, type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { makeOwner, type Owner } from '~/routes/safe/store/models/owner'
|
||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { safesInitialState } from '~/routes/safe/store/reducer/safe'
|
||||
import { setOwners, OWNERS_KEY } from '~/utils/storage'
|
||||
|
|
|
@ -4,7 +4,7 @@ import { aNewStore } from '~/store'
|
|||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||
import { confirmationsTransactionSelector, safeTransactionsSelector } from '~/routes/safe/store/selectors'
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { getGnosisSafeInstanceAt } from '~/logic/contracts/safeContracts'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { NAME_PARAM, OWNER_ADDRESS_PARAM, INCREASE_PARAM } from '~/routes/safe/component/AddOwner/AddOwnerForm'
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// @flow
|
||||
import { List } from 'immutable'
|
||||
import { getSafeEthereumInstance, createTransaction } from '~/logic/safe/safeFrontendOperations'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { makeOwner } from '~/routes/safe/store/model/owner'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
import { makeOwner } from '~/routes/safe/store/models/owner'
|
||||
import fetchTransactions from '~/routes/safe/store/actions/fetchTransactions'
|
||||
import { makeConfirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { makeConfirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import { aNewStore } from '~/store'
|
||||
import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
||||
import { getSafeFrom } from '~/test/utils/safeHelper'
|
||||
|
|
|
@ -10,7 +10,7 @@ import { aMinedSafe } from '~/test/builder/safe.redux.builder'
|
|||
import { travelToTokens } from '~/test/builder/safe.dom.utils'
|
||||
import { sleep } from '~/utils/timer'
|
||||
import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
||||
import { tokenListSelector, activeTokensSelector } from '~/logic/tokens/store/selectors'
|
||||
import { tokenListSelector } from '~/logic/tokens/store/selectors'
|
||||
import { getActiveTokenAddresses } from '~/logic/tokens/utils/tokensStorage'
|
||||
import { enableFirstToken, testToken } from '~/test/builder/tokens.dom.utils'
|
||||
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
|
||||
|
|
|
@ -10,7 +10,6 @@ import { testToken } from '~/test/builder/tokens.dom.utils'
|
|||
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import addToken from '~/logic/tokens/store/actions/addToken'
|
||||
import { activeTokensSelector } from '~/logic/tokens/store/selectors'
|
||||
|
||||
describe('DOM > Feature > Add new ERC 20 Tokens', () => {
|
||||
// let web3
|
||||
|
|
|
@ -10,7 +10,6 @@ import { testToken } from '~/test/builder/tokens.dom.utils'
|
|||
import * as fetchTokensModule from '~/logic/tokens/store/actions/fetchTokens'
|
||||
import * as enhancedFetchModule from '~/utils/fetch'
|
||||
import addToken from '~/logic/tokens/store/actions/addToken'
|
||||
import { activeTokensSelector, tokenListSelector } from '~/logic/tokens/store/selectors'
|
||||
import removeTokenAction from '~/logic/tokens/store/actions/removeToken'
|
||||
import { makeToken } from '~/logic/tokens/store/model/token'
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import { List, Map } from 'immutable'
|
||||
import { type Confirmation } from '~/routes/safe/store/model/confirmation'
|
||||
import { type Transaction } from '~/routes/safe/store/model/transaction'
|
||||
import { type Confirmation } from '~/routes/safe/store/models/confirmation'
|
||||
import { type Transaction } from '~/routes/safe/store/models/transaction'
|
||||
import { sameAddress } from '~/logic/wallets/ethAddresses'
|
||||
|
||||
export const testSizeOfSafesWith = (transactions: Map<string, List<Transaction>>, size: number) => {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { buildMathPropsFrom } from '~/test/utils/buildReactRouterProps'
|
|||
import { safeSelector } from '~/routes/safe/store/selectors/index'
|
||||
import { type Match } from 'react-router-dom'
|
||||
import { type GlobalState } from '~/store'
|
||||
import { type Safe } from '~/routes/safe/store/model/safe'
|
||||
import { type Safe } from '~/routes/safe/store/models/safe'
|
||||
|
||||
export const getSafeFrom = (state: GlobalState, safeAddress: string): Safe => {
|
||||
const match: Match = buildMathPropsFrom(safeAddress)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
// @flow
|
||||
import {
|
||||
ImmortalStorage, CookieStore, IndexedDbStore, LocalStorageStore, SessionStorageStore,
|
||||
} from 'immortal-db'
|
||||
import { ImmortalStorage, IndexedDbStore, LocalStorageStore } from 'immortal-db'
|
||||
|
||||
const stores = [CookieStore, IndexedDbStore, LocalStorageStore, SessionStorageStore]
|
||||
// Don't use sessionStorage and cookieStorage
|
||||
// https://github.com/gruns/ImmortalDB/issues/22
|
||||
// https://github.com/gruns/ImmortalDB/issues/6
|
||||
const stores = [IndexedDbStore, LocalStorageStore]
|
||||
export const storage = new ImmortalStorage(stores)
|
||||
|
||||
const PREFIX = 'v1'
|
||||
|
|
Loading…
Reference in New Issue