Merge pull request #97 from gnosis/83-tokens

83-Tokens progress: Internal refactorings
This commit is contained in:
Mikhail Mikheev 2019-04-08 11:00:25 +04:00 committed by GitHub
commit 5d05861856
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2351 additions and 34538 deletions

32863
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -66,7 +66,7 @@
}, },
"dependencies": { "dependencies": {
"@gnosis.pm/util-contracts": "^2.0.0", "@gnosis.pm/util-contracts": "^2.0.0",
"@material-ui/core": "^3.0.1", "@material-ui/core": "^3.9.3",
"@material-ui/icons": "^3.0.1", "@material-ui/icons": "^3.0.1",
"axios": "^0.18.0", "axios": "^0.18.0",
"bignumber.js": "^8.1.1", "bignumber.js": "^8.1.1",
@ -80,7 +80,7 @@
"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.0", "react-hot-loader": "^4.8.2",
"react-infinite-scroll-component": "^4.5.2", "react-infinite-scroll-component": "^4.5.2",
"react-redux": "^6.0.1", "react-redux": "^6.0.1",
"react-router-dom": "^4.3.1", "react-router-dom": "^4.3.1",
@ -117,10 +117,10 @@
"@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.5", "@storybook/addon-actions": "^5.0.6",
"@storybook/addon-knobs": "^5.0.5", "@storybook/addon-knobs": "^5.0.6",
"@storybook/addon-links": "^5.0.5", "@storybook/addon-links": "^5.0.6",
"@storybook/react": "^5.0.5", "@storybook/react": "^5.0.6",
"autoprefixer": "^9.4.10", "autoprefixer": "^9.4.10",
"babel-core": "^7.0.0-bridge.0", "babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
@ -132,7 +132,7 @@
"classnames": "^2.2.5", "classnames": "^2.2.5",
"css-loader": "^2.1.0", "css-loader": "^2.1.0",
"detect-port": "^1.2.2", "detect-port": "^1.2.2",
"eslint": "^5.15.3", "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.4.2",
"eslint-plugin-import": "^2.9.0", "eslint-plugin-import": "^2.9.0",
@ -142,7 +142,7 @@
"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.95.1", "flow-bin": "0.96.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",
@ -158,9 +158,9 @@
"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.9", "truffle": "^5.0.10",
"truffle-contract": "^4.0.10", "truffle-contract": "^4.0.11",
"truffle-solidity-loader": "^0.1.9", "truffle-solidity-loader": "^0.1.10",
"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.1.0",

View File

@ -5,8 +5,6 @@ import { List } from 'immutable'
import Row from '~/components/layout/Row' import Row from '~/components/layout/Row'
import Table from '@material-ui/core/Table' import Table from '@material-ui/core/Table'
import TableBody from '@material-ui/core/TableBody' import TableBody from '@material-ui/core/TableBody'
import TableRow from '@material-ui/core/TableRow'
import TableCell from '@material-ui/core/TableCell'
import { withStyles } from '@material-ui/core/styles' import { withStyles } from '@material-ui/core/styles'
import CircularProgress from '@material-ui/core/CircularProgress' import CircularProgress from '@material-ui/core/CircularProgress'
import TablePagination from '@material-ui/core/TablePagination' import TablePagination from '@material-ui/core/TablePagination'
@ -136,14 +134,7 @@ class GnoTable<K> extends React.Component<Props<K>, State> {
{!isEmpty && ( {!isEmpty && (
<Table aria-labelledby={label} className={classes.root}> <Table aria-labelledby={label} className={classes.root}>
<TableHead columns={columns} order={order} orderBy={orderByParam} onSort={this.onSort} /> <TableHead columns={columns} order={order} orderBy={orderByParam} onSort={this.onSort} />
<TableBody> <TableBody>{children(sortedData)}</TableBody>
{children(sortedData)}
{emptyRows > 0 && (
<TableRow style={this.getEmptyStyle(emptyRows)}>
<TableCell colSpan={4} />
</TableRow>
)}
</TableBody>
</Table> </Table>
)} )}
{isEmpty && ( {isEmpty && (

View File

@ -4,5 +4,9 @@ import 'babel-polyfill'
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import Root from '~/components/Root' import Root from '~/components/Root'
import { store } from '~/store'
import loadSafesFromStorage from '~/routes/safe/store/actions/loadSafesFromStorage'
store.dispatch(loadSafesFromStorage())
ReactDOM.render(<Root />, document.getElementById('root')) ReactDOM.render(<Root />, document.getElementById('root'))

View File

@ -5,7 +5,7 @@ import { type Operation, submitOperation } from '~/logic/safe/safeTxHistory'
import { getSafeEthereumInstance } from '~/logic/safe/safeFrontendOperations' import { getSafeEthereumInstance } from '~/logic/safe/safeFrontendOperations'
import { buildSignaturesFrom } from '~/logic/safe/safeTxSigner' import { buildSignaturesFrom } from '~/logic/safe/safeTxSigner'
import { generateMetamaskSignature, generateTxGasEstimateFrom, estimateDataGas } from '~/logic/safe/safeTxSignerEIP712' import { generateMetamaskSignature, generateTxGasEstimateFrom, estimateDataGas } from '~/logic/safe/safeTxSignerEIP712'
import { storeSignature, getSignaturesFrom } from '~/utils/localStorage/signatures' import { storeSignature, getSignaturesFrom } from '~/utils/storage/signatures'
import { signaturesViaMetamask } from '~/config' import { signaturesViaMetamask } from '~/config'
export const approveTransaction = async ( export const approveTransaction = async (

View File

@ -6,7 +6,7 @@ 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/model/safe'
import { getGnosisSafeContract } from '~/logic/contracts/safeContracts' import { getGnosisSafeContract } from '~/logic/contracts/safeContracts'
import { storeSubject } from '~/utils/localStorage/transactions' import { storeSubject } from '~/utils/storage/transactions'
export const TX_NAME_PARAM = 'txName' export const TX_NAME_PARAM = 'txName'
export const TX_DESTINATION_PARAM = 'txDestination' export const TX_DESTINATION_PARAM = 'txDestination'

View File

@ -2,7 +2,7 @@
import { getWeb3 } from '~/logic/wallets/getWeb3' import { getWeb3 } from '~/logic/wallets/getWeb3'
import { BigNumber } from 'bignumber.js' import { BigNumber } from 'bignumber.js'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { getSignaturesFrom } from '~/utils/localStorage/signatures' import { getSignaturesFrom } from '~/utils/storage/signatures'
const estimateDataGasCosts = (data) => { const estimateDataGasCosts = (data) => {
const reducer = (accumulator, currentValue) => { const reducer = (accumulator, currentValue) => {

View File

@ -0,0 +1,3 @@
// @flow
export * from './safeStorage'

View File

@ -0,0 +1,43 @@
// @flow
import { type Owner } from '~/routes/safe/store/model/owner'
import { List, Map } from 'immutable'
import { loadFromStorage, saveToStorage } from '~/utils/storage'
export const SAFES_KEY = 'SAFES'
export const TX_KEY = 'TX'
export const OWNERS_KEY = 'OWNERS'
export const getSafeName = async (safeAddress: string) => {
const safes = await loadFromStorage(SAFES_KEY)
if (!safes) {
return undefined
}
const safe = safes[safeAddress]
return safe ? safe.name : undefined
}
export const saveSafes = async (safes: Object) => {
try {
await saveToStorage(SAFES_KEY, safes)
} catch (err) {
// eslint-disable-next-line
console.log('Error storing safe info in localstorage')
}
}
export const setOwners = async (safeAddress: string, owners: List<Owner>) => {
try {
const ownersAsMap = Map(owners.map((owner: Owner) => [owner.get('address').toLowerCase(), owner.get('name')]))
await saveToStorage(`${OWNERS_KEY}-${safeAddress}`, ownersAsMap)
} catch (err) {
// eslint-disable-next-line
console.log('Error storing owners in localstorage')
}
}
export const getOwners = async (safeAddress: string): Map<string, string> => {
const data: Object = await loadFromStorage(`${OWNERS_KEY}-${safeAddress}`)
return data ? Map(data) : Map()
}

View File

@ -1,7 +1,7 @@
// @flow // @flow
import { List } from 'immutable' import { List } from 'immutable'
import { ImmortalDB } from 'immortal-db'
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'
export const ACTIVE_TOKENS_KEY = 'ACTIVE_TOKENS' export const ACTIVE_TOKENS_KEY = 'ACTIVE_TOKENS'
export const TOKENS_KEY = 'TOKENS' export const TOKENS_KEY = 'TOKENS'
@ -11,9 +11,8 @@ const getTokensKey = (safeAddress: string) => `${TOKENS_KEY}-${safeAddress}`
export const setActiveTokens = async (safeAddress: string, tokens: List<TokenProps>) => { export const setActiveTokens = async (safeAddress: string, tokens: List<TokenProps>) => {
try { try {
const serializedState = JSON.stringify(tokens.toJS())
const key = getActiveTokensKey(safeAddress) const key = getActiveTokensKey(safeAddress)
await ImmortalDB.set(key, serializedState) 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')
@ -22,25 +21,24 @@ export const setActiveTokens = async (safeAddress: string, tokens: List<TokenPro
export const getActiveTokens = async (safeAddress: string): Promise<List<TokenProps>> => { export const getActiveTokens = async (safeAddress: string): Promise<List<TokenProps>> => {
const key = getActiveTokensKey(safeAddress) const key = getActiveTokensKey(safeAddress)
const data = await ImmortalDB.get(key) const data = await loadFromStorage(key)
return data ? List(JSON.parse(data)) : List() return data ? List(data) : List()
} }
export const getTokens = async (safeAddress: string): Promise<List<TokenProps>> => { export const getTokens = async (safeAddress: string): Promise<List<TokenProps>> => {
const key = getTokensKey(safeAddress) const key = getTokensKey(safeAddress)
const data = await ImmortalDB.get(key) const data = await loadFromStorage(key)
return data ? List(JSON.parse(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 getTokens(safeAddress)
try { try {
const serializedState = JSON.stringify(data.push(token))
const key = getTokensKey(safeAddress) const key = getTokensKey(safeAddress)
await ImmortalDB.set(key, serializedState) 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')
@ -52,9 +50,8 @@ export const removeTokenFromStorage = async (safeAddress: string, token: Token)
try { try {
const index = data.indexOf(token) const index = data.indexOf(token)
const serializedState = JSON.stringify(data.remove(index))
const key = getTokensKey(safeAddress) const key = getTokensKey(safeAddress)
await ImmortalDB.set(key, serializedState) 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')

View File

@ -3,7 +3,8 @@ 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 { buildSafe } from '~/routes/safe/store/actions/fetchSafe' import { buildSafe } from '~/routes/safe/store/actions/fetchSafe'
import { SAFES_KEY, load, saveSafes } from '~/utils/localStorage' import { SAFES_KEY, saveSafes } from '~/logic/safe/utils'
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'
@ -18,7 +19,7 @@ export const loadSafe = async (safeName: string, safeAddress: string, updateSafe
await updateSafe(safeRecord) await updateSafe(safeRecord)
const storedSafes = load(SAFES_KEY) || {} const storedSafes = await loadFromStorage(SAFES_KEY) || {}
storedSafes[safeAddress] = safeRecord.toJSON() storedSafes[safeAddress] = safeRecord.toJSON()
saveSafes(storedSafes) saveSafes(storedSafes)

View File

@ -5,7 +5,7 @@ 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/model/safe'
import { type Owner, makeOwner } from '~/routes/safe/store/model/owner' import { type Owner, makeOwner } from '~/routes/safe/store/model/owner'
import { setOwners } from '~/utils/localStorage' 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'
import Review from './Review' import Review from './Review'

View File

@ -1,8 +1,12 @@
// @flow // @flow
import { List } from 'immutable' import { List } from 'immutable'
import { createAction } from 'redux-actions' import { createAction } from 'redux-actions'
import { type SafeProps } from '~/routes/safe/store/model/safe' import { type Safe, makeSafe } from '~/routes/safe/store/model/safe'
import { saveSafes, setOwners } from '~/logic/safe/utils'
import { makeOwner, type Owner } from '~/routes/safe/store/model/owner' import { makeOwner, type Owner } from '~/routes/safe/store/model/owner'
import type { Dispatch as ReduxDispatch, GetState } from 'redux'
import { type GlobalState } from '~/store/index'
import { safesMapSelector } from '~/routes/safeList/store/selectors/index'
export const ADD_SAFE = 'ADD_SAFE' export const ADD_SAFE = 'ADD_SAFE'
@ -12,18 +16,40 @@ export const buildOwnersFrom = (names: Array<string>, addresses: Array<string>)
return List(owners) return List(owners)
} }
const addSafe = createAction( type ActionReturn = {
ADD_SAFE, safe: Safe,
(name: string, address: string, threshold: number, ownersName: string[], ownersAddress: string[]): SafeProps => { }
const owners: List<Owner> = buildOwnersFrom(ownersName, ownersAddress)
return { export const addSafe = createAction<string, *, *>(
address, ADD_SAFE,
name, (safe: Safe): ActionReturn => ({
threshold, safe,
owners, }),
}
},
) )
export default addSafe const saveSafe = (
name: string,
address: string,
threshold: number,
ownersName: string[],
ownersAddress: string[],
) => async (dispatch: ReduxDispatch<GlobalState>, getState: GetState<GlobalState>) => {
const owners: List<Owner> = buildOwnersFrom(ownersName, ownersAddress)
const state: GlobalState = getState()
const safe: Safe = makeSafe({
name,
address,
threshold,
owners,
})
const safes = safesMapSelector(state)
const newSafes = safes.set(address, safe)
setOwners(address, owners)
saveSafes(newSafes.toJSON())
dispatch(addSafe(safe))
}
export default saveSafe

View File

@ -3,4 +3,4 @@ import { createAction } from 'redux-actions'
export const ADD_TRANSACTIONS = 'ADD_TRANSACTIONS' export const ADD_TRANSACTIONS = 'ADD_TRANSACTIONS'
export default createAction(ADD_TRANSACTIONS) export default createAction<string, *>(ADD_TRANSACTIONS)

View File

@ -5,7 +5,7 @@ import { type GlobalState } from '~/store/index'
import { makeOwner } from '~/routes/safe/store/model/owner' import { makeOwner } from '~/routes/safe/store/model/owner'
import { type SafeProps, makeSafe } from '~/routes/safe/store/model/safe' import { type SafeProps, makeSafe } from '~/routes/safe/store/model/safe'
import updateSafe from '~/routes/safe/store/actions/updateSafe' import updateSafe from '~/routes/safe/store/actions/updateSafe'
import { getOwners, getSafeName } from '~/utils/localStorage' 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 } from '~/logic/wallets/getWeb3'
@ -16,11 +16,11 @@ const buildOwnersFrom = (safeOwners: string[], storedOwners: Map<string, string>
export const buildSafe = async (safeAddress: string, safeName: string) => { export const buildSafe = async (safeAddress: string, safeName: string) => {
const web3 = getWeb3() const web3 = getWeb3()
const GnosisSafe = await getGnosisSafeContract(web3) const SafeContract = await getGnosisSafeContract(web3)
const gnosisSafe = await GnosisSafe.at(safeAddress) const gnosisSafe = await SafeContract.at(safeAddress)
const threshold = Number(await gnosisSafe.getThreshold()) const threshold = Number(await gnosisSafe.getThreshold())
const owners = List(buildOwnersFrom(await gnosisSafe.getOwners(), getOwners(safeAddress))) const owners = List(buildOwnersFrom(await gnosisSafe.getOwners(), await getOwners(safeAddress)))
const safe: SafeProps = { const safe: SafeProps = {
address: safeAddress, address: safeAddress,
@ -34,7 +34,7 @@ export const buildSafe = async (safeAddress: string, safeName: string) => {
export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => { export default (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
try { try {
const safeName = getSafeName(safeAddress) || 'LOADED SAFE' const safeName = await getSafeName(safeAddress) || 'LOADED SAFE'
const safeRecord = await buildSafe(safeAddress, safeName) const safeRecord = await buildSafe(safeAddress, safeName)
return dispatch(updateSafe(safeRecord)) return dispatch(updateSafe(safeRecord))

View File

@ -6,9 +6,9 @@ import { type GlobalState } from '~/store/index'
import { makeOwner } from '~/routes/safe/store/model/owner' import { makeOwner } from '~/routes/safe/store/model/owner'
import { makeTransaction, type Transaction } from '~/routes/safe/store/model/transaction' import { makeTransaction, type Transaction } from '~/routes/safe/store/model/transaction'
import { makeConfirmation } from '~/routes/safe/store/model/confirmation' import { makeConfirmation } from '~/routes/safe/store/model/confirmation'
import { loadSafeSubjects } from '~/utils/localStorage/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 '~/utils/localStorage' import { getOwners } from '~/logic/safe/utils'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions' import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import addTransactions from './addTransactions' import addTransactions from './addTransactions'

View File

@ -0,0 +1,25 @@
// @flow
import type { Dispatch as ReduxDispatch } from 'redux'
import { type GlobalState } from '~/store/index'
import { SAFES_KEY } from '~/logic/safe/utils'
import { type SafeProps } from '~/routes/safe/store/model/safe'
import { loadFromStorage } from '~/utils/storage'
import { addSafe } from './addSafe'
import { buildSafe } from '~/routes/safe/store/reducer/safe'
export default () => async (dispatch: ReduxDispatch<GlobalState>) => {
try {
const safes: ?{ [string]: SafeProps } = await loadFromStorage(SAFES_KEY)
if (safes) {
Object.values(safes).forEach((safeProps: SafeProps) => {
dispatch(addSafe(buildSafe(safeProps)))
})
}
} catch (err) {
// eslint-disable-next-line
console.error('Error while getting safes from storage:', err)
}
return Promise.resolve()
}

View File

@ -3,6 +3,6 @@ import { createAction } from 'redux-actions'
export const UPDATE_SAFE = 'UPDATE_SAFE' export const UPDATE_SAFE = 'UPDATE_SAFE'
const updateSafe = createAction(UPDATE_SAFE) const updateSafe = createAction<string, *>(UPDATE_SAFE)
export default updateSafe export default updateSafe

View File

@ -3,6 +3,6 @@ import { createAction } from 'redux-actions'
export const UPDATE_SAFES = 'UPDATE_SAFES' export const UPDATE_SAFES = 'UPDATE_SAFES'
const updateSafesInBatch = createAction(UPDATE_SAFES) const updateSafesInBatch = createAction<string, *>(UPDATE_SAFES)
export default updateSafesInBatch export default updateSafesInBatch

View File

@ -1,13 +1,12 @@
// @flow // @flow
import { Map } from 'immutable' import { Map } from 'immutable'
import { handleActions, type ActionType } from 'redux-actions' import { handleActions, type ActionType } from 'redux-actions'
import addSafe, { 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 { type Safe, type SafeProps, makeSafe } from '~/routes/safe/store/model/safe'
import { type OwnerProps } from '~/routes/safe/store/model/owner' import { type OwnerProps } from '~/routes/safe/store/model/owner'
import { import { loadFromStorage } from '~/utils/storage'
saveSafes, setOwners, load, SAFES_KEY, import { SAFES_KEY } from '~/logic/safe/utils'
} from '~/utils/localStorage' import { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
import updateSafe, { UPDATE_SAFE } from '~/routes/safe/store/actions/updateSafe'
export const SAFE_REDUCER_ID = 'safes' export const SAFE_REDUCER_ID = 'safes'
@ -46,8 +45,8 @@ const buildSafesFrom = (loadedSafes: Object): Map<string, Safe> => {
} }
} }
export const safesInitialState = (): State => { export const safesInitialState = async (): Promise<State> => {
const storedSafes = load(SAFES_KEY) const storedSafes = await loadFromStorage(SAFES_KEY)
const safes = storedSafes ? buildSafesFrom(storedSafes) : Map() const safes = storedSafes ? buildSafesFrom(storedSafes) : Map()
return safes return safes
@ -55,7 +54,7 @@ export const safesInitialState = (): State => {
export default handleActions<State, *>( export default handleActions<State, *>(
{ {
[UPDATE_SAFE]: (state: State, action: ActionType<typeof updateSafe>): 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.get('address')
@ -66,14 +65,10 @@ export default handleActions<State, *>(
return state.set(safeAddress, safe) return state.set(safeAddress, safe)
}, },
[ADD_SAFE]: (state: State, action: ActionType<typeof addSafe>): State => { [ADD_SAFE]: (state: State, action: ActionType<Function>): State => {
const safe: Safe = makeSafe(action.payload) const { safe }: { safe: Safe } = action.payload
setOwners(safe.get('address'), safe.get('owners'))
const safes = state.set(action.payload.address, safe) return state.set(safe.address, safe)
saveSafes(safes.toJSON())
return safes
}, },
}, },
Map(), Map(),

View File

@ -18,4 +18,4 @@ const SafeList = ({ safes, provider }: Props) => (
</Page> </Page>
) )
export default connect(selector)(SafeList) export default connect<*, *, *, *>(selector)(SafeList)

View File

@ -6,8 +6,9 @@ import { type Safe } from '~/routes/safe/store/model/safe'
import { userAccountSelector } from '~/logic/wallets/store/selectors' import { userAccountSelector } from '~/logic/wallets/store/selectors'
import { type Owner } from '~/routes/safe/store/model/owner' import { type Owner } from '~/routes/safe/store/model/owner'
import { sameAddress } from '~/logic/wallets/ethAddresses' import { sameAddress } from '~/logic/wallets/ethAddresses'
import { SAFE_REDUCER_ID } from '~/routes/safe/store/reducer/safe'
export const safesMapSelector = (state: GlobalState): Map<string, Safe> => state.safes export const safesMapSelector = (state: GlobalState): Map<string, Safe> => state[SAFE_REDUCER_ID]
const safesListSelector: Selector<GlobalState, {}, List<Safe>> = createSelector( const safesListSelector: Selector<GlobalState, {}, List<Safe>> = createSelector(
safesMapSelector, safesMapSelector,

View File

@ -6,7 +6,7 @@ import {
} from 'redux' } from 'redux'
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, safesInitialState } from '~/routes/safe/store/reducer/safe' import safe, { SAFE_REDUCER_ID, type State as SafeState } from '~/routes/safe/store/reducer/safe'
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,
@ -36,10 +36,6 @@ const reducers: Reducer<GlobalState> = combineReducers({
[TRANSACTIONS_REDUCER_ID]: transactions, [TRANSACTIONS_REDUCER_ID]: transactions,
}) })
const initialState = { export const store: Store<GlobalState> = createStore(reducers, finalCreateStore)
[SAFE_REDUCER_ID]: safesInitialState(),
}
export const store: Store<GlobalState> = createStore(reducers, initialState, finalCreateStore)
export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(reducers, localState, finalCreateStore) export const aNewStore = (localState?: Object): Store<GlobalState> => createStore(reducers, localState, finalCreateStore)

View File

@ -9,7 +9,7 @@ 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/model/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/localStorage' import { setOwners, OWNERS_KEY } from '~/utils/storage'
describe('Safe - redux load safe', () => { describe('Safe - redux load safe', () => {
let store let store
@ -40,7 +40,7 @@ describe('Safe - redux load safe', () => {
expect(safe.get('address')).toBe(safeAddress) expect(safe.get('address')).toBe(safeAddress)
expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'UNKNOWN', address: accounts[0] })])) expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'UNKNOWN', address: accounts[0] })]))
expect(safesInitialState()).toEqual(safes) expect(await safesInitialState()).toEqual(safes)
}) })
it('if safe is not present but owners, store and persist it with stored names', async () => { it('if safe is not present but owners, store and persist it with stored names', async () => {
@ -64,7 +64,7 @@ describe('Safe - redux load safe', () => {
expect(safe.get('address')).toBe(safeAddress) expect(safe.get('address')).toBe(safeAddress)
expect(safe.get('owners')).toEqual(List([makeOwner({ name: ownerName, address: accounts[0] })])) expect(safe.get('owners')).toEqual(List([makeOwner({ name: ownerName, address: accounts[0] })]))
expect(safesInitialState()).toEqual(safes) expect(await safesInitialState()).toEqual(safes)
}) })
it('if safe is present but no owners, store and persist it with default names', async () => { it('if safe is present but no owners, store and persist it with default names', async () => {
@ -86,7 +86,7 @@ describe('Safe - redux load safe', () => {
expect(safe.get('address')).toBe(safeAddress) expect(safe.get('address')).toBe(safeAddress)
expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'UNKNOWN', address: accounts[0] })])) expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'UNKNOWN', address: accounts[0] })]))
expect(safesInitialState()).toEqual(safes) expect(await safesInitialState()).toEqual(safes)
}) })
it('if safe is present but owners, store and persist it with stored names', async () => { it('if safe is present but owners, store and persist it with stored names', async () => {
@ -107,6 +107,6 @@ describe('Safe - redux load safe', () => {
expect(safe.get('address')).toBe(safeAddress) expect(safe.get('address')).toBe(safeAddress)
expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'Adol 1 Eth Account', address: accounts[0] })])) expect(safe.get('owners')).toEqual(List([makeOwner({ name: 'Adol 1 Eth Account', address: accounts[0] })]))
expect(safesInitialState()).toEqual(safes) expect(await safesInitialState()).toEqual(safes)
}) })
}) })

View File

@ -1,61 +0,0 @@
// @flow
import { List, Map } from 'immutable'
import { type Owner } from '~/routes/safe/store/model/owner'
export const SAFES_KEY = 'SAFES'
export const TX_KEY = 'TX'
export const OWNERS_KEY = 'OWNERS'
export const load = (key: string) => {
try {
const serializedState = localStorage.getItem(key)
if (serializedState === null) {
return undefined
}
if (serializedState === undefined) {
return undefined
}
return JSON.parse(serializedState)
} catch (err) {
return undefined
}
}
export const getSafeName = (safeAddress: string) => {
const safes = load(SAFES_KEY)
if (!safes) {
return undefined
}
const safe = safes[safeAddress]
return safe ? safe.name : undefined
}
export const saveSafes = (safes: Object) => {
try {
const serializedState = JSON.stringify(safes)
localStorage.setItem(SAFES_KEY, serializedState)
} catch (err) {
// eslint-disable-next-line
console.log('Error storing safe info in localstorage')
}
}
export const setOwners = (safeAddress: string, owners: List<Owner>) => {
try {
const ownersAsMap = Map(owners.map((owner: Owner) => [owner.get('address').toLowerCase(), owner.get('name')]))
const serializedState = JSON.stringify(ownersAsMap)
localStorage.setItem(`${OWNERS_KEY}-${safeAddress}`, serializedState)
} catch (err) {
// eslint-disable-next-line
console.log('Error storing owners in localstorage')
}
}
export const getOwners = (safeAddress: string): Map<string, string> => {
const data = load(`${OWNERS_KEY}-${safeAddress}`)
return data ? Map(data) : Map()
}

View File

@ -0,0 +1,32 @@
// @flow
import {
ImmortalStorage, CookieStore, IndexedDbStore, LocalStorageStore, SessionStorageStore,
} from 'immortal-db'
const stores = [CookieStore, IndexedDbStore, LocalStorageStore, SessionStorageStore]
export const storage = new ImmortalStorage(stores)
const PREFIX = 'v1'
export const loadFromStorage = async (key: string): Promise<*> => {
try {
const stringifiedValue = await storage.get(`${PREFIX}__${key}`)
if (stringifiedValue === null || stringifiedValue === undefined) {
return undefined
}
return JSON.parse(stringifiedValue)
} catch (err) {
console.error(`Failed to load ${key} from storage:`, err)
return undefined
}
}
export const saveToStorage = async (key: string, value: *): Promise<*> => {
try {
const stringifiedValue = JSON.stringify(value)
await storage.set(`${PREFIX}__${key}`, stringifiedValue)
} catch (err) {
console.error(`Failed to save ${key} in the storage:`, err)
}
}

View File

@ -1,20 +1,19 @@
// @flow // @flow
import { Map } from 'immutable' import { Map } from 'immutable'
import { load } from '~/utils/localStorage' import { loadFromStorage, saveToStorage } from '~/utils/storage'
const getSignaturesKeyFrom = (safeAddress: string) => `TXS-SIGNATURES-${safeAddress}` const getSignaturesKeyFrom = (safeAddress: string) => `TXS-SIGNATURES-${safeAddress}`
export const storeSignature = (safeAddress: string, nonce: number, signature: string) => { export const storeSignature = async (safeAddress: string, nonce: number, signature: string) => {
const signaturesKey = getSignaturesKeyFrom(safeAddress) const signaturesKey = getSignaturesKeyFrom(safeAddress)
const subjects = Map(load(signaturesKey)) || Map() const subjects = Map(await loadFromStorage(signaturesKey)) || Map()
try { try {
const key = `${nonce}` const key = `${nonce}`
const existingSignatures = subjects.get(key) const existingSignatures = subjects.get(key)
const signatures = existingSignatures ? existingSignatures + signature : signature const signatures = existingSignatures ? existingSignatures + signature : signature
const updatedSubjects = subjects.set(key, signatures) const updatedSubjects = subjects.set(key, signatures)
const serializedState = JSON.stringify(updatedSubjects) await saveToStorage(signaturesKey, updatedSubjects)
localStorage.setItem(signaturesKey, serializedState)
} catch (err) { } catch (err) {
// eslint-disable-next-line // eslint-disable-next-line
console.log('Error storing signatures in localstorage') console.log('Error storing signatures in localstorage')
@ -23,7 +22,7 @@ export const storeSignature = (safeAddress: string, nonce: number, signature: st
export const getSignaturesFrom = (safeAddress: string, nonce: number) => { export const getSignaturesFrom = (safeAddress: string, nonce: number) => {
const key = getSignaturesKeyFrom(safeAddress) const key = getSignaturesKeyFrom(safeAddress)
const data: any = load(key) const data: any = loadFromStorage(key)
const signatures = data ? Map(data) : Map() const signatures = data ? Map(data) : Map()
const txSigs = signatures.get(String(nonce)) || '' const txSigs = signatures.get(String(nonce)) || ''

View File

@ -1,17 +1,16 @@
// @flow // @flow
import { Map } from 'immutable' import { Map } from 'immutable'
import { load } from '~/utils/localStorage' import { loadFromStorage, saveToStorage } from '~/utils/storage'
const getSubjectKeyFrom = (safeAddress: string) => `TXS-SUBJECTS-${safeAddress}` const getSubjectKeyFrom = (safeAddress: string) => `TXS-SUBJECTS-${safeAddress}`
export const storeSubject = (safeAddress: string, nonce: number, subject: string) => { export const storeSubject = async (safeAddress: string, nonce: number, subject: string) => {
const key = getSubjectKeyFrom(safeAddress) const key = getSubjectKeyFrom(safeAddress)
const subjects = Map(load(key)) || Map() const subjects = Map(await loadFromStorage(key)) || Map()
try { try {
const updatedSubjects = subjects.set(nonce, subject) const updatedSubjects = subjects.set(nonce, subject)
const serializedState = JSON.stringify(updatedSubjects) saveToStorage(key, updatedSubjects)
localStorage.setItem(key, serializedState)
} catch (err) { } catch (err) {
// eslint-disable-next-line // eslint-disable-next-line
console.log('Error storing transaction subject in localstorage') console.log('Error storing transaction subject in localstorage')
@ -20,7 +19,7 @@ export const storeSubject = (safeAddress: string, nonce: number, subject: string
export const loadSafeSubjects = (safeAddress: string): Map<string, string> => { export const loadSafeSubjects = (safeAddress: string): Map<string, string> => {
const key = getSubjectKeyFrom(safeAddress) const key = getSubjectKeyFrom(safeAddress)
const data: any = load(key) const data: any = loadFromStorage(key)
return data ? Map(data) : Map() return data ? Map(data) : Map()
} }

3651
yarn.lock

File diff suppressed because it is too large Load Diff