WA-232 Storing in localStorage active tokens
This commit is contained in:
parent
be9bfe0df9
commit
84057d03ee
|
@ -7,11 +7,11 @@ import GnoSafe from './Safe'
|
|||
type Props = SelectorProps
|
||||
|
||||
const Layout = ({
|
||||
safe, tokens, provider, userAddress,
|
||||
safe, activeTokens, provider, userAddress,
|
||||
}: Props) => (
|
||||
<React.Fragment>
|
||||
{ safe
|
||||
? <GnoSafe safe={safe} tokens={tokens} userAddress={userAddress} />
|
||||
? <GnoSafe safe={safe} tokens={activeTokens} userAddress={userAddress} />
|
||||
: <NoSafe provider={provider} text="Not found safe" />
|
||||
}
|
||||
</React.Fragment>
|
||||
|
|
|
@ -30,7 +30,7 @@ storiesOf('Routes /safe:address', module)
|
|||
userAddress="foo"
|
||||
safe={undefined}
|
||||
provider="METAMASK"
|
||||
tokens={Map()}
|
||||
activeTokens={Map()}
|
||||
fetchBalance={() => {}}
|
||||
/>
|
||||
))
|
||||
|
@ -39,7 +39,7 @@ storiesOf('Routes /safe:address', module)
|
|||
userAddress="foo"
|
||||
safe={undefined}
|
||||
provider=""
|
||||
tokens={Map()}
|
||||
activeTokens={Map()}
|
||||
fetchBalance={() => {}}
|
||||
/>
|
||||
))
|
||||
|
@ -51,7 +51,7 @@ storiesOf('Routes /safe:address', module)
|
|||
userAddress="foo"
|
||||
safe={safe}
|
||||
provider="METAMASK"
|
||||
tokens={Map().set('ETH', ethBalance)}
|
||||
activeTokens={Map().set('ETH', ethBalance)}
|
||||
fetchBalance={() => {}}
|
||||
/>
|
||||
)
|
||||
|
@ -64,7 +64,7 @@ storiesOf('Routes /safe:address', module)
|
|||
userAddress="foo"
|
||||
safe={safe}
|
||||
provider="METAMASK"
|
||||
tokens={Map().set('ETH', ethBalance)}
|
||||
activeTokens={Map().set('ETH', ethBalance)}
|
||||
fetchBalance={() => {}}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -16,7 +16,9 @@ const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 15000
|
|||
class SafeView extends React.PureComponent<Props> {
|
||||
componentDidMount() {
|
||||
this.intervalId = setInterval(() => {
|
||||
const { safe, fetchTokens, fetchSafe } = this.props
|
||||
const {
|
||||
safe, fetchTokens, fetchSafe,
|
||||
} = this.props
|
||||
if (!safe) {
|
||||
return
|
||||
}
|
||||
|
@ -45,13 +47,13 @@ class SafeView extends React.PureComponent<Props> {
|
|||
|
||||
render() {
|
||||
const {
|
||||
safe, provider, tokens, granted, userAddress,
|
||||
safe, provider, activeTokens, granted, userAddress,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<Page>
|
||||
{ granted
|
||||
? <Layout tokens={tokens} provider={provider} safe={safe} userAddress={userAddress} />
|
||||
? <Layout activeTokens={activeTokens} provider={provider} safe={safe} userAddress={userAddress} />
|
||||
: <NoRights />
|
||||
}
|
||||
</Page>
|
||||
|
|
|
@ -7,13 +7,13 @@ import { type Safe } from '~/routes/safe/store/model/safe'
|
|||
import { type Owner } from '~/routes/safe/store/model/owner'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { sameAddress } from '~/wallets/ethAddresses'
|
||||
import { tokensSelector } from '~/routes/tokens/store/selectors'
|
||||
import { activeTokensSelector } from '~/routes/tokens/store/selectors'
|
||||
import { type Token } from '~/routes/tokens/store/model/token'
|
||||
|
||||
export type SelectorProps = {
|
||||
safe: SafeSelectorProps,
|
||||
provider: string,
|
||||
tokens: Map<string, Token>,
|
||||
activeTokens: Map<string, Token>,
|
||||
userAddress: string,
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ export const grantedSelector: Selector<GlobalState, RouterProps, boolean> = crea
|
|||
export default createStructuredSelector({
|
||||
safe: safeSelector,
|
||||
provider: providerNameSelector,
|
||||
tokens: tokensSelector,
|
||||
activeTokens: activeTokensSelector,
|
||||
granted: grantedSelector,
|
||||
userAddress: userAccountSelector,
|
||||
})
|
||||
|
|
|
@ -44,7 +44,7 @@ const styles = () => ({
|
|||
|
||||
class TokenComponent extends React.Component<Props, State> {
|
||||
state = {
|
||||
checked: true,
|
||||
checked: this.props.token.get('status'),
|
||||
}
|
||||
|
||||
// onRemoveClick = () => this.props.onRemoveToken(this.props.token)
|
||||
|
|
|
@ -9,6 +9,7 @@ const disableToken = createAction(
|
|||
(safeAddress: string, token: Token) => ({
|
||||
safeAddress,
|
||||
symbol: token.get('symbol'),
|
||||
address: token.get('address'),
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ const enableToken = createAction(
|
|||
(safeAddress: string, token: Token) => ({
|
||||
safeAddress,
|
||||
symbol: token.get('symbol'),
|
||||
address: token.get('address'),
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
// @flow
|
||||
import { Map } from 'immutable'
|
||||
import { List, Map } from 'immutable'
|
||||
import contract from 'truffle-contract'
|
||||
import type { Dispatch as ReduxDispatch } from 'redux'
|
||||
import StandardToken from '@gnosis.pm/util-contracts/build/contracts/StandardToken.json'
|
||||
import { getBalanceInEtherOf, getWeb3 } from '~/wallets/getWeb3'
|
||||
import { getWeb3 } from '~/wallets/getWeb3'
|
||||
import { type GlobalState } from '~/store/index'
|
||||
import { makeToken, type Token, type TokenProps } from '~/routes/tokens/store/model/token'
|
||||
import logo from '~/assets/icons/icon_etherTokens.svg'
|
||||
import { ensureOnce } from '~/utils/singleton'
|
||||
import { getTokens } from '~/utils/localStorage/tokens'
|
||||
import { getSafeEthToken } from '~/utils/tokens'
|
||||
import { enhancedFetch } from '~/utils/fetch'
|
||||
import addTokens from './addTokens'
|
||||
|
||||
|
||||
const createStandardTokenContract = async () => {
|
||||
const web3 = getWeb3()
|
||||
const erc20Token = await contract(StandardToken)
|
||||
|
@ -29,49 +30,33 @@ export const calculateBalanceOf = async (tokenAddress: string, address: string,
|
|||
.catch(() => '0')
|
||||
}
|
||||
|
||||
export const fetchTokens = (safeAddress: string) => async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const balance = await getBalanceInEtherOf(safeAddress)
|
||||
const ethBalance = makeToken({
|
||||
address: '0',
|
||||
name: 'Ether',
|
||||
symbol: 'ETH',
|
||||
decimals: 18,
|
||||
logoUrl: logo,
|
||||
funds: balance,
|
||||
})
|
||||
export const fetchTokens = (safeAddress: string) =>
|
||||
async (dispatch: ReduxDispatch<GlobalState>) => {
|
||||
const tokens: List<string> = getTokens(safeAddress)
|
||||
const ethBalance = await getSafeEthToken(safeAddress)
|
||||
|
||||
const header = new Headers({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
})
|
||||
const url = 'https://gist.githubusercontent.com/rmeissner/98911fcf74b0ea9731e2dae2441c97a4/raw/'
|
||||
const errMsg = 'Error querying safe balances'
|
||||
const json = await enhancedFetch(url, errMsg)
|
||||
|
||||
const sentData = {
|
||||
mode: 'cors',
|
||||
header,
|
||||
try {
|
||||
const balancesRecords = await Promise.all(json.map(async (item: TokenProps) => {
|
||||
const funds = await calculateBalanceOf(item.address, safeAddress, item.decimals)
|
||||
const status = tokens.includes(item.address)
|
||||
return makeToken({ ...item, status, funds })
|
||||
}))
|
||||
|
||||
const balances: Map<string, Token> = Map().withMutations((map) => {
|
||||
balancesRecords.forEach(record => map.set(record.get('symbol'), record))
|
||||
|
||||
map.set('ETH', ethBalance)
|
||||
})
|
||||
|
||||
return dispatch(addTokens(safeAddress, balances))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error fetching token balances... " + err)
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch('https://gist.githubusercontent.com/rmeissner/98911fcf74b0ea9731e2dae2441c97a4/raw/', sentData)
|
||||
if (!response.ok) {
|
||||
throw new Error('Error querying safe balances')
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
|
||||
try {
|
||||
const balancesRecords = await Promise.all(json.map(async (item: TokenProps) => {
|
||||
const funds = await calculateBalanceOf(item.address, safeAddress, item.decimals)
|
||||
return makeToken({ ...item, funds })
|
||||
}))
|
||||
|
||||
const balances: Map<string, Token> = Map().withMutations((map) => {
|
||||
balancesRecords.forEach(record => map.set(record.get('symbol'), record))
|
||||
map.set('ETH', ethBalance)
|
||||
})
|
||||
|
||||
return dispatch(addTokens(safeAddress, balances))
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.log("Error fetching token balances...")
|
||||
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,50 @@
|
|||
// @flow
|
||||
import { Map } from 'immutable'
|
||||
import { List, Map } from 'immutable'
|
||||
import { handleActions, type ActionType } from 'redux-actions'
|
||||
import addTokens, { ADD_TOKENS } from '~/routes/tokens/store/actions/addTokens'
|
||||
import { type Token } from '~/routes/tokens/store/model/token'
|
||||
import disableToken, { DISABLE_TOKEN } from '~/routes/tokens/store/actions/disableToken'
|
||||
import enableToken, { ENABLE_TOKEN } from '~/routes/tokens/store/actions/enableToken'
|
||||
import { setTokens, getTokens } from '~/utils/localStorage/tokens'
|
||||
import { ensureOnce } from '~/utils/singleton'
|
||||
import { calculateActiveErc20TokensFrom } from '~/utils/tokens'
|
||||
|
||||
export const TOKEN_REDUCER_ID = 'tokens'
|
||||
|
||||
export type State = Map<string, Map<string, Token>>
|
||||
|
||||
const setTokensOnce = ensureOnce(setTokens)
|
||||
|
||||
export default handleActions({
|
||||
[ADD_TOKENS]: (state: State, action: ActionType<typeof addTokens>): State =>
|
||||
state.update(action.payload.safeAddress, (prevSafe: Map<string, Token>) => {
|
||||
[ADD_TOKENS]: (state: State, action: ActionType<typeof addTokens>): State => {
|
||||
const { safeAddress, tokens } = action.payload
|
||||
|
||||
const activeAddresses: List<Token> = calculateActiveErc20TokensFrom(tokens.toList())
|
||||
setTokensOnce(safeAddress, activeAddresses)
|
||||
|
||||
return state.update(safeAddress, (prevSafe: Map<string, Token>) => {
|
||||
if (!prevSafe) {
|
||||
return action.payload.tokens
|
||||
return tokens
|
||||
}
|
||||
|
||||
return prevSafe.equals(action.payload.tokens) ? prevSafe : action.payload.tokens
|
||||
}),
|
||||
[DISABLE_TOKEN]: (state: State, action: ActionType<typeof disableToken>): State =>
|
||||
state.setIn([action.payload.safeAddress, action.payload.symbol, 'status'], false),
|
||||
[ENABLE_TOKEN]: (state: State, action: ActionType<typeof enableToken>): State =>
|
||||
state.setIn([action.payload.safeAddress, action.payload.symbol, 'status'], true),
|
||||
return prevSafe.equals(tokens) ? prevSafe : tokens
|
||||
})
|
||||
},
|
||||
[DISABLE_TOKEN]: (state: State, action: ActionType<typeof disableToken>): State => {
|
||||
const { address, safeAddress, symbol } = action.payload
|
||||
|
||||
const activeTokens = getTokens(safeAddress)
|
||||
const index = activeTokens.indexOf(address)
|
||||
setTokens(safeAddress, activeTokens.delete(index))
|
||||
|
||||
return state.setIn([safeAddress, symbol, 'status'], false)
|
||||
},
|
||||
[ENABLE_TOKEN]: (state: State, action: ActionType<typeof enableToken>): State => {
|
||||
const { address, safeAddress, symbol } = action.payload
|
||||
|
||||
const activeTokens = getTokens(safeAddress)
|
||||
setTokens(safeAddress, activeTokens.push(address))
|
||||
|
||||
return state.setIn([safeAddress, symbol, 'status'], true)
|
||||
},
|
||||
}, Map())
|
||||
|
|
|
@ -5,24 +5,30 @@ import { safeParamAddressSelector, type RouterProps } from '~/routes/safe/store/
|
|||
import { type GlobalState } from '~/store'
|
||||
import { TOKEN_REDUCER_ID } from '~/routes/tokens/store/reducer/tokens'
|
||||
import { type Token } from '~/routes/tokens/store/model/token'
|
||||
import { calculateActiveErc20TokensFrom } from '~/utils/tokens'
|
||||
|
||||
const balancesSelector = (state: GlobalState) => state[TOKEN_REDUCER_ID]
|
||||
|
||||
export const tokensSelector: Selector<GlobalState, RouterProps, Map<string, Token>> = createSelector(
|
||||
balancesSelector,
|
||||
safeParamAddressSelector,
|
||||
(balances: Map<string, Map<string, Token>>, address: string) => {
|
||||
(tokens: Map<string, Map<string, Token>>, address: string) => {
|
||||
if (!address) {
|
||||
return Map()
|
||||
}
|
||||
|
||||
return balances.get(address) || Map()
|
||||
return tokens.get(address) || Map()
|
||||
},
|
||||
)
|
||||
|
||||
export const tokenListSelector = createSelector(
|
||||
tokensSelector,
|
||||
(balances: Map<string, Token>) => balances.toList(),
|
||||
(tokens: Map<string, Token>) => tokens.toList(),
|
||||
)
|
||||
|
||||
export const activeTokensSelector = createSelector(
|
||||
tokenListSelector,
|
||||
(tokens: List<Token>) => tokens.filter((token: Token) => token.get('status')),
|
||||
)
|
||||
|
||||
export const tokenAddressesSelector = createSelector(
|
||||
|
@ -34,3 +40,8 @@ export const tokenAddressesSelector = createSelector(
|
|||
return addresses
|
||||
},
|
||||
)
|
||||
|
||||
export const activeTokenAddressesSelector = createSelector(
|
||||
tokenListSelector,
|
||||
(balances: List<Token>) => calculateActiveErc20TokensFrom(balances),
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue