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