diff --git a/src/routes/index.js b/src/routes/index.js
index 4bc05bb0..42b6f74c 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -4,13 +4,18 @@ import Loadable from 'react-loadable'
import { Switch, Redirect, Route } from 'react-router-dom'
import Loader from '~/components/Loader'
import Welcome from './welcome/container'
-import { SAFELIST_ADDRESS, OPEN_ADDRESS, SAFE_PARAM_ADDRESS, WELCOME_ADDRESS } from './routes'
+import { SAFELIST_ADDRESS, OPEN_ADDRESS, SAFE_PARAM_ADDRESS, WELCOME_ADDRESS, SETTINS_ADDRESS } from './routes'
const Safe = Loadable({
loader: () => import('./safe/container'),
loading: Loader,
})
+const Settings = Loadable({
+ loader: () => import('./tokens/container'),
+ loading: Loader,
+})
+
const SafeList = Loadable({
loader: () => import('./safeList/container'),
loading: Loader,
@@ -22,6 +27,9 @@ const Open = Loadable({
})
const SAFE_ADDRESS = `${SAFELIST_ADDRESS}/:${SAFE_PARAM_ADDRESS}`
+const SAFE_SETTINGS = `${SAFE_ADDRESS}${SETTINS_ADDRESS}`
+
+export const settingsUrlFrom = (safeAddress: string) => `${SAFELIST_ADDRESS}/${safeAddress}/settings`
const Routes = () => (
@@ -30,6 +38,7 @@ const Routes = () => (
+
)
diff --git a/src/routes/routes.js b/src/routes/routes.js
index ed521627..4ab227a3 100644
--- a/src/routes/routes.js
+++ b/src/routes/routes.js
@@ -3,3 +3,4 @@ export const SAFE_PARAM_ADDRESS = 'address'
export const SAFELIST_ADDRESS = '/safes'
export const OPEN_ADDRESS = '/open'
export const WELCOME_ADDRESS = '/welcome'
+export const SETTINS_ADDRESS = '/settings'
diff --git a/src/routes/safe/component/Layout.jsx b/src/routes/safe/component/Layout.jsx
index 4ae7803f..231731cf 100644
--- a/src/routes/safe/component/Layout.jsx
+++ b/src/routes/safe/component/Layout.jsx
@@ -7,11 +7,11 @@ import GnoSafe from './Safe'
type Props = SelectorProps
const Layout = ({
- safe, balances, provider, userAddress,
+ safe, activeTokens, provider, userAddress,
}: Props) => (
{ safe
- ?
+ ?
:
}
diff --git a/src/routes/safe/component/Layout.stories.js b/src/routes/safe/component/Layout.stories.js
index 0c1cb5e9..86e8f8a5 100644
--- a/src/routes/safe/component/Layout.stories.js
+++ b/src/routes/safe/component/Layout.stories.js
@@ -4,7 +4,7 @@ import * as React from 'react'
import { Map } from 'immutable'
import styles from '~/components/layout/PageFrame/index.scss'
import { SafeFactory } from '~/routes/safe/store/test/builder/safe.builder'
-import { makeBalance } from '~/routes/safe/store/model/balance'
+import { makeToken } from '~/routes/tokens/store/model/token'
import Component from './Layout'
@@ -14,7 +14,7 @@ const FrameDecorator = story => (
)
-const ethBalance = makeBalance({
+const ethBalance = makeToken({
address: '0',
name: 'Ether',
symbol: 'ETH',
@@ -30,7 +30,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={undefined}
provider="METAMASK"
- balances={Map()}
+ activeTokens={Map()}
fetchBalance={() => {}}
/>
))
@@ -39,7 +39,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={undefined}
provider=""
- balances={Map()}
+ activeTokens={Map()}
fetchBalance={() => {}}
/>
))
@@ -51,7 +51,7 @@ storiesOf('Routes /safe:address', module)
userAddress="foo"
safe={safe}
provider="METAMASK"
- balances={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"
- balances={Map().set('ETH', ethBalance)}
+ activeTokens={Map().set('ETH', ethBalance)}
fetchBalance={() => {}}
/>
)
diff --git a/src/routes/safe/component/Safe/BalanceInfo.jsx b/src/routes/safe/component/Safe/BalanceInfo.jsx
index dc46ac49..10399168 100644
--- a/src/routes/safe/component/Safe/BalanceInfo.jsx
+++ b/src/routes/safe/component/Safe/BalanceInfo.jsx
@@ -1,7 +1,9 @@
// @flow
import * as React from 'react'
import classNames from 'classnames'
+import Link from '~/components/layout/Link'
import AccountBalance from '@material-ui/icons/AccountBalance'
+import Settings from '@material-ui/icons/Settings'
import Avatar from '@material-ui/core/Avatar'
import Collapse from '@material-ui/core/Collapse'
import IconButton from '@material-ui/core/IconButton'
@@ -17,11 +19,13 @@ import { Map } from 'immutable'
import Button from '~/components/layout/Button'
import openHoc, { type Open } from '~/components/hoc/OpenHoc'
import { type WithStyles } from '~/theme/mui'
-import { type Balance } from '~/routes/safe/store/model/balance'
+import { type Token } from '~/routes/tokens/store/model/token'
+import { settingsUrlFrom } from '~/routes'
type Props = Open & WithStyles & {
- balances: Map,
- onMoveFunds: (balance: Balance) => void,
+ safeAddress: string,
+ tokens: Map,
+ onMoveFunds: (token: Token) => void,
}
const styles = {
@@ -33,9 +37,10 @@ const styles = {
export const MOVE_FUNDS_BUTTON_TEXT = 'Move'
const BalanceComponent = openHoc(({
- open, toggle, balances, classes, onMoveFunds,
+ open, toggle, tokens, classes, onMoveFunds, safeAddress,
}: Props) => {
- const hasBalances = balances.count() > 0
+ const hasBalances = tokens.count() > 0
+ const settingsUrl = settingsUrlFrom(safeAddress)
return (
@@ -44,6 +49,11 @@ const BalanceComponent = openHoc(({
+
+
+
+
+
{open
?
@@ -53,18 +63,18 @@ const BalanceComponent = openHoc(({
- {balances.valueSeq().map((balance: Balance) => {
- const symbol = balance.get('symbol')
- const name = balance.get('name')
- const disabled = Number(balance.get('funds')) === 0
- const onMoveFundsClick = () => onMoveFunds(balance)
+ {tokens.valueSeq().map((token: Token) => {
+ const symbol = token.get('symbol')
+ const name = token.get('name')
+ const disabled = Number(token.get('funds')) === 0
+ const onMoveFundsClick = () => onMoveFunds(token)
return (
-
+
-
+
diff --git a/src/routes/safe/component/Safe/index.jsx b/src/routes/safe/component/Safe/index.jsx
index f3404c60..232cfee3 100644
--- a/src/routes/safe/component/Safe/index.jsx
+++ b/src/routes/safe/component/Safe/index.jsx
@@ -9,7 +9,7 @@ import Img from '~/components/layout/Img'
import Paragraph from '~/components/layout/Paragraph'
import Row from '~/components/layout/Row'
import { type Safe } from '~/routes/safe/store/model/safe'
-import { type Balance } from '~/routes/safe/store/model/balance'
+import { type Token } from '~/routes/tokens/store/model/token'
import Withdraw from '~/routes/safe/component/Withdraw'
import Transactions from '~/routes/safe/component/Transactions'
@@ -30,7 +30,7 @@ const safeIcon = require('./assets/gnosis_safe.svg')
type SafeProps = {
safe: Safe,
- balances: Map,
+ tokens: Map,
userAddress: string,
}
@@ -42,13 +42,13 @@ const listStyle = {
width: '100%',
}
-const getEthBalanceFrom = (balances: Map) => {
- const ethBalance = balances.get('ETH')
- if (!ethBalance) {
+const getEthBalanceFrom = (tokens: List) => {
+ const ethToken = tokens.filter(token => token.get('symbol') === 'ETH')
+ if (ethToken.count() === 0) {
return 0
}
- return Number(ethBalance.get('funds'))
+ return Number(ethToken.get(0).get('funds'))
}
class GnoSafe extends React.PureComponent {
@@ -93,13 +93,13 @@ class GnoSafe extends React.PureComponent {
this.setState({ component: })
}
- onMoveTokens = (ercToken: Balance) => {
+ onMoveTokens = (ercToken: Token) => {
const { safe } = this.props
this.setState({
component: ,
@@ -107,15 +107,16 @@ class GnoSafe extends React.PureComponent {
}
render() {
- const { safe, balances, userAddress } = this.props
+ const { safe, tokens, userAddress } = this.props
const { component } = this.state
- const ethBalance = getEthBalanceFrom(balances)
+ const ethBalance = getEthBalanceFrom(tokens)
+ const address = safe.get('address')
return (
-
+
{
onRemoveOwner={this.onRemoveOwner}
/>
-
+
diff --git a/src/routes/safe/component/SendToken/index.jsx b/src/routes/safe/component/SendToken/index.jsx
index 3b60a08e..0258f977 100644
--- a/src/routes/safe/component/SendToken/index.jsx
+++ b/src/routes/safe/component/SendToken/index.jsx
@@ -5,11 +5,12 @@ import { connect } from 'react-redux'
import Stepper from '~/components/Stepper'
import { sleep } from '~/utils/timer'
import { type Safe } from '~/routes/safe/store/model/safe'
-import { type Balance } from '~/routes/safe/store/model/balance'
+import { getStandardTokenContract } from '~/routes/tokens/store/actions/fetchTokens'
+import { type Token } from '~/routes/tokens/store/model/token'
import { createTransaction } from '~/wallets/createTransactions'
-import { getStandardTokenContract } from '~/routes/safe/store/actions/fetchBalances'
import { EMPTY_DATA } from '~/wallets/ethTransactions'
import { toNative } from '~/wallets/tokens'
+import { isEther } from '~/utils/tokens'
import actions, { type Actions } from './actions'
import selector, { type SelectorProps } from './selector'
import SendTokenForm, { TKN_DESTINATION_PARAM, TKN_VALUE_PARAM } from './SendTokenForm'
@@ -21,7 +22,7 @@ const getSteps = () => [
type Props = SelectorProps & Actions & {
safe: Safe,
- balance: Balance,
+ token: Token,
onReset: () => void,
}
@@ -31,8 +32,6 @@ type State = {
export const SEE_TXS_BUTTON_TEXT = 'VISIT TXS'
-const isEther = (symbol: string) => symbol === 'ETH'
-
const getTransferData = async (tokenAddress: string, to: string, amount: BigNumber) => {
const StandardToken = await getStandardTokenContract()
const myToken = await StandardToken.at(tokenAddress)
@@ -40,16 +39,16 @@ const getTransferData = async (tokenAddress: string, to: string, amount: BigNumb
return myToken.contract.transfer.getData(to, amount)
}
-const processTokenTransfer = async (safe: Safe, balance: Balance, to: string, amount: number, userAddress: string) => {
- const symbol = balance.get('symbol')
+const processTokenTransfer = async (safe: Safe, token: Token, to: string, amount: number, userAddress: string) => {
+ const symbol = token.get('symbol')
const nonce = Date.now()
- const name = `Send ${amount} ${balance.get('symbol')} to ${to}`
+ const name = `Send ${amount} ${token.get('symbol')} to ${to}`
const value = isEther(symbol) ? amount : 0
- const tokenAddress = balance.get('address')
+ const tokenAddress = token.get('address')
const destination = isEther(symbol) ? to : tokenAddress
const data = isEther(symbol)
? EMPTY_DATA
- : await getTransferData(tokenAddress, to, await toNative(amount, balance.get('decimals')))
+ : await getTransferData(tokenAddress, to, await toNative(amount, token.get('decimals')))
return createTransaction(safe, name, destination, value, nonce, userAddress, data)
}
@@ -61,12 +60,12 @@ class SendToken extends React.Component {
onTransaction = async (values: Object) => {
try {
- const { safe, balance, userAddress } = this.props
+ const { safe, token, userAddress } = this.props
const amount = values[TKN_VALUE_PARAM]
const destination = values[TKN_DESTINATION_PARAM]
- await processTokenTransfer(safe, balance, destination, amount, userAddress)
+ await processTokenTransfer(safe, token, destination, amount, userAddress)
await sleep(1500)
this.props.fetchTransactions()
this.setState({ done: true })
@@ -84,10 +83,10 @@ class SendToken extends React.Component {
render() {
const { done } = this.state
- const { balance } = this.props
+ const { token } = this.props
const steps = getSteps()
const finishedButton =
- const symbol = balance.get('symbol')
+ const symbol = token.get('symbol')
return (
@@ -98,7 +97,7 @@ class SendToken extends React.Component {
steps={steps}
onReset={this.onReset}
>
-
+
{ SendTokenForm }
diff --git a/src/routes/safe/container/actions.js b/src/routes/safe/container/actions.js
index ff9a91b4..7924354b 100644
--- a/src/routes/safe/container/actions.js
+++ b/src/routes/safe/container/actions.js
@@ -1,13 +1,13 @@
// @flow
import fetchSafe from '~/routes/safe/store/actions/fetchSafe'
-import { fetchBalances } from '~/routes/safe/store/actions/fetchBalances'
+import { fetchTokens } from '~/routes/tokens/store/actions/fetchTokens'
export type Actions = {
fetchSafe: typeof fetchSafe,
- fetchBalances: typeof fetchBalances,
+ fetchTokens: typeof fetchTokens,
}
export default {
fetchSafe,
- fetchBalances,
+ fetchTokens,
}
diff --git a/src/routes/safe/container/index.jsx b/src/routes/safe/container/index.jsx
index 2b1c9d0d..b980ec2a 100644
--- a/src/routes/safe/container/index.jsx
+++ b/src/routes/safe/container/index.jsx
@@ -16,12 +16,14 @@ const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 15000
class SafeView extends React.PureComponent {
componentDidMount() {
this.intervalId = setInterval(() => {
- const { safe, fetchBalances, fetchSafe } = this.props
+ const {
+ safe, fetchTokens, fetchSafe,
+ } = this.props
if (!safe) {
return
}
const safeAddress = safe.get('address')
- fetchBalances(safeAddress)
+ fetchTokens(safeAddress)
fetchSafe(safe)
}, TIMEOUT)
}
@@ -33,7 +35,7 @@ class SafeView extends React.PureComponent {
if (this.props.safe) {
const safeAddress = this.props.safe.get('address')
- this.props.fetchBalances(safeAddress)
+ this.props.fetchTokens(safeAddress)
}
}
@@ -45,13 +47,13 @@ class SafeView extends React.PureComponent {
render() {
const {
- safe, provider, balances, granted, userAddress,
+ safe, provider, activeTokens, granted, userAddress,
} = this.props
return (
{ granted
- ?
+ ?
:
}
diff --git a/src/routes/safe/container/selector.js b/src/routes/safe/container/selector.js
index 45410f57..94b76a07 100644
--- a/src/routes/safe/container/selector.js
+++ b/src/routes/safe/container/selector.js
@@ -1,18 +1,19 @@
// @flow
import { List, Map } from 'immutable'
import { createSelector, createStructuredSelector, type Selector } from 'reselect'
-import { balanceSelector, safeSelector, type RouterProps, type SafeSelectorProps } from '~/routes/safe/store/selectors'
+import { safeSelector, type RouterProps, type SafeSelectorProps } from '~/routes/safe/store/selectors'
import { providerNameSelector, userAccountSelector } from '~/wallets/store/selectors/index'
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 { type Balance } from '~/routes/safe/store/model/balance'
+import { activeTokensSelector } from '~/routes/tokens/store/selectors'
+import { type Token } from '~/routes/tokens/store/model/token'
export type SelectorProps = {
safe: SafeSelectorProps,
provider: string,
- balances: Map,
+ activeTokens: Map,
userAddress: string,
}
@@ -40,7 +41,7 @@ export const grantedSelector: Selector = crea
export default createStructuredSelector({
safe: safeSelector,
provider: providerNameSelector,
- balances: balanceSelector,
+ activeTokens: activeTokensSelector,
granted: grantedSelector,
userAddress: userAccountSelector,
})
diff --git a/src/routes/safe/store/actions/addBalances.js b/src/routes/safe/store/actions/addBalances.js
deleted file mode 100644
index eb473d1e..00000000
--- a/src/routes/safe/store/actions/addBalances.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// @flow
-import { Map } from 'immutable'
-import { createAction } from 'redux-actions'
-import { type Balance } from '~/routes/safe/store/model/balance'
-
-export const ADD_BALANCES = 'ADD_BALANCES'
-
-type BalanceProps = {
- safeAddress: string,
- balances: Map,
-}
-
-const addBalances = createAction(
- ADD_BALANCES,
- (safeAddress: string, balances: Map): BalanceProps => ({
- safeAddress,
- balances,
- }),
-)
-
-export default addBalances
diff --git a/src/routes/safe/store/actions/fetchBalances.js b/src/routes/safe/store/actions/fetchBalances.js
deleted file mode 100644
index 76f2705a..00000000
--- a/src/routes/safe/store/actions/fetchBalances.js
+++ /dev/null
@@ -1,65 +0,0 @@
-// @flow
-import { 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 { type GlobalState } from '~/store/index'
-import { makeBalance, type Balance, type BalanceProps } from '~/routes/safe/store/model/balance'
-import logo from '~/assets/icons/icon_etherTokens.svg'
-import addBalances from './addBalances'
-
-export const getStandardTokenContract = async () => {
- const web3 = getWeb3()
- const erc20Token = await contract(StandardToken)
- erc20Token.setProvider(web3.currentProvider)
-
- return erc20Token
-}
-
-export const calculateBalanceOf = async (tokenAddress: string, address: string, decimals: number) => {
- const erc20Token = await getStandardTokenContract()
-
- return erc20Token.at(tokenAddress)
- .then(instance => instance.balanceOf(address).then(funds => funds.div(10 ** decimals).toString()))
- .catch(() => '0')
-}
-
-export const fetchBalances = (safeAddress: string) => async (dispatch: ReduxDispatch) => {
- const balance = await getBalanceInEtherOf(safeAddress)
- const ethBalance = makeBalance({
- address: '0',
- name: 'Ether',
- symbol: 'ETH',
- decimals: 18,
- logoUrl: logo,
- funds: balance,
- })
-
- const header = new Headers({
- 'Access-Control-Allow-Origin': '*',
- })
-
- const sentData = {
- mode: 'cors',
- header,
- }
-
- 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()
- return Promise.all(json.map(async (item: BalanceProps) => {
- const funds = await calculateBalanceOf(item.address, safeAddress, item.decimals)
- return makeBalance({ ...item, funds })
- })).then((balancesRecords) => {
- const balances: Map = Map().withMutations((map) => {
- balancesRecords.forEach(record => map.set(record.get('symbol'), record))
- map.set('ETH', ethBalance)
- })
-
- return dispatch(addBalances(safeAddress, balances))
- })
-}
diff --git a/src/routes/safe/store/actions/fetchSafe.js b/src/routes/safe/store/actions/fetchSafe.js
index 9e9a48ff..7f41f8b9 100644
--- a/src/routes/safe/store/actions/fetchSafe.js
+++ b/src/routes/safe/store/actions/fetchSafe.js
@@ -37,7 +37,14 @@ export const buildSafe = async (storedSafe: Object) => {
}
export default (safe: Safe) => async (dispatch: ReduxDispatch) => {
- const safeRecord = await buildSafe(safe.toJSON())
+ try {
+ const safeRecord = await buildSafe(safe.toJSON())
- return dispatch(updateSafe(safeRecord))
+ return dispatch(updateSafe(safeRecord))
+ } catch (err) {
+ // eslint-disable-next-line
+ console.log("Error while updating safe information")
+
+ return Promise.resolve()
+ }
}
diff --git a/src/routes/safe/store/actions/fetchSafes.js b/src/routes/safe/store/actions/fetchSafes.js
index 196c1b40..45633a22 100644
--- a/src/routes/safe/store/actions/fetchSafes.js
+++ b/src/routes/safe/store/actions/fetchSafes.js
@@ -11,15 +11,23 @@ const buildSafesFrom = async (loadedSafes: Object): Promise